小黄

黄小黄的幸福生活!


  • 首页

  • 标签

  • 分类

  • 归档

  • Java

数据库整理

发表于 2019-04-21 | 分类于 database

什么是存储过程?有什么优缺点?

存储过程是一些预编译的SQL语句。也就是说存储过程是一个记录集,它是由一些T-SQL语句组成的代码块,这些T-SQL语句代码像一个方法一样实现一些功能(比如对单表的CRUD),然后再给这个代码块取一个名字,在用到这个功能的时候调用就可以。

  • 存储过程是一个预编译的代码块,执行效率比较高
  • 一个存储过程替代大量T-SQL语句,可以降低网络通信量,提高通信速率。
  • 可以一定程度上确保数据安全。

索引是什么?有什么作用及优缺点?

  • 索引是对数据库表中一列或多列的值进行排序的结构,是帮助MySQL高效获取数据的数据结构。

  • 索引就是加快检索表中数据的方法。数据库的索引类似于书籍的索引。在书籍中,索引允许用户不必翻阅整个书就能迅速地找到所需要的信息。在数据库中,索引也允许数据库程序迅速地找到表中的数据,而不必扫描整个数据库。

  • MySQL数据库几个基本的索引类型:

    • 普通索引:没有任何限制,最基本的索引,创建方式有以下几种

      • 直接创建索引:CREATE INDEX index_name ON table(column(length))

      • 修改表结构的方式添加索引:ALTER TABLE table_name ADD INDEX index_name ON (column(length))

      • 创建表的时候创建索引:

        1
        2
        3
        4
        5
        6
        7
        8
        CREATE TABLE `table` (
        `id` int(11) NOT NULL AUTO_INCREMENT ,
        `title` char(255) CHARACTER NOT NULL ,
        `content` text CHARACTER NULL ,
        `time` int(10) NULL DEFAULT NULL ,
        PRIMARY KEY (`id`),
        INDEX index_name (title(length))
        )
      • 删除索引:DROP INDEX index_name ON table

    • 主键索引:一种特殊的唯一索引,一个表只能有一个主键,不允许有空值。一般在建表的同时创建主键索引:

      1
      2
      3
      4
      5
      CREATE TABLE `table` (
      `id` int(11) NOT NULL AUTO_INCREMENT ,
      `title` char(255) NOT NULL ,
      PRIMARY KEY (`id`)
      );
    • 唯一索引:索引列的值必须是唯一的,允许有控制。如果是组合索引,则列的组合必须唯一,创建方式:

      • 创建唯一索引:CREATE UNIQUE INDEX indexName ON table(column(length))

      • 修改表结构的时候指定唯一索引:ALTER TABLE table_name ADD UNIQUE indexName ON (column(length))

      • 创建表的时候直接指定:

        1
        2
        3
        4
        5
        6
        7
        CREATE TABLE `table` (
        `id` int(11) NOT NULL AUTO_INCREMENT ,
        `title` char(255) CHARACTER NOT NULL ,
        `content` text CHARACTER NULL ,
        `time` int(10) NULL DEFAULT NULL ,
        UNIQUE indexName (title(length))
        );
    • 组合索引:多个字段上创建的索引,只有在查询条件中使用了创建索引时的第一个字段,索引才会被使用,使用组合索引时遵循最左前缀集合:ALTER TABLEtableADD INDEX name_city_age (name,city,age);

    • 全文索引:主要用来查找文本中的关键字,而不是直接参与索引中的值的比较。fulltext索引和其他索引不太一样,更像一个搜索引擎,而不是一个简单的where语句的参数匹配。fulltext索引配合match against操作使用,而不是一般的where语句加like。全文索引可以在建表,修改表,创建索引中使用,不过目前只有cahr,varchar,text列上可以创建全文索引。而当数据量较大时,将现有数据放入一个没有全局索引的表中,然后再create index创建全文索引,要比先为一张表创建全文索引然后再将数据写入速度快很多。

      • 创建表时添加全文索引:

        1
        2
        3
        4
        5
        6
        7
        8
        CREATE TABLE `table` (
        `id` int(11) NOT NULL AUTO_INCREMENT ,
        `title` char(255) CHARACTER NOT NULL ,
        `content` text CHARACTER NULL ,
        `time` int(10) NULL DEFAULT NULL ,
        PRIMARY KEY (`id`),
        FULLTEXT (content)
        );
      • 修改表结构添加全文索引:ALTER TABLE article ADD FULLTEXT index_content(content)

      • 直接创建索引:CREATE FULLTEXT INDEX index_content ON article(content)

  • 优缺点:

    • 索引加快数据库的检索速度
    • 索引降低了插入,删除,修改等维护任务的速度
    • 唯一索引可以确保每一行数据的唯一性
    • 通过使用索引,可以在查询的过程中使用优化隐藏器,提高系统的性能。
    • 索引需要占物理和数据空间

什么是事务?

  • 事务是并发控制的基本单位。所谓的事务,它指的是一个操作序列,这些操作要么都执行,要么都不执行。它是一个不可分割的工作单位。事务是数据库维护数据一致性的单位,在每个事务结束时,都能保持数据的一致性。

数据库的乐观锁AND悲观锁是什么?

  • 数据库管理系统DBMS中并发控制的任务是确保在多个事务同时存取数据库中同一数据时不破坏事务的隔离性和统一性以及数据库的统一性。
  • 乐观锁和悲观锁是并发控制主要采用的技术手段:
    • 乐观锁:假定不会发生并发冲突,只在提交操作时检查是否违反数据完整性。
    • 悲观锁:假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作。

使用索引查询一定能提高查询的性能吗?为什么?

  • 通常来说,通过索引查询数据比全表扫描要快。
  • 但是索引需要空间来存储,也需要定期维护。每当有记录在表中增减或者索引列被修改时,索引本身也会被修改。意味着每条记录的insert,delete,update方法将会为此多付出4,5次的磁盘IO。
  • 因为索引需要额外的存储空间和处理,那些不必要的索引反而会使查询反应时间变慢。使用索引不一定能提高查询性能。
  • 索引范围性查找适用于两种情况:

    • 基于一个范围的检索,一般返回结果集小于表中记录的30%。
    • 基于唯一性索引的检索。

MySQL数据库索引失效的原因:

  • 对单字段建立了索引,where条件多字段
  • 建立了联合索引,where条件单字段
  • 对索引列运算,运算包括(+、-、*、/、!、<>、%、like’%_‘、or、in、exist)导致索引失效
  • 类型错误,如字段类型为varchar,where条件用number
  • 对应索引内部函数,这种情况下应该建立基于函数的索引
  • 查询表的效率要比查询索引快的情况
  • is null索引失效,is not null betree索引生效。

说一下drop、delete、truncate的区别:

  • delete和truncate只删除表的数据不删除表的结构
  • 速度来说:drop>truncate>delete
  • delete语句是dml,这个操作会放到rollback segment中,事务提交之后才生效。如果有相应的trigger,执行的时候将被出发。truncate和drop是ddl,操作立即生效,原数据不放到rollback segment中,不能回滚也不能触发trigger。

drop、delete、truncate的使用场景?

  • drop,不再需要一张表时
  • delete,向删除部分数据行时,并带上where子句
  • truncate,保留表结构而删除所有数据的时候

超键,候选键,主键,外键分别是什么

GET
超键 在关系中能唯一标识元组的属性集成为关系模式的超键。一个属性可以作为一个超键,多个属性组合在一起也可以作为一个超键。超键包含候选键和主键
候选键 是最小的超键,即没有冗余元素的超键
主键 数据库表中对存储数据对象给予唯一和完整标识的数据列或者属性的集合。一个数据列只能有一个主键,且主键的取值不能缺少,即不能为空值(null)
外键 在一个表中存在另一个表的主键称为此表的外键

什么是视图?以及视图的使用场景?

  • 视图是一张虚拟的表,具有和物理表相同的功能。可以对视图进行CRUD,视图通常是由一个表或者多个表的行或列的子集。对视图的修改不影响基本表。它使得我们获取数据更容易,相比多表查询。

  • 只暴露部分字段给访问者,所以就建一个虚表,也就是一个视图。

  • 查询的数据来源于不同的表,而查询者希望以统一的方式查询,这样可以建立一个视图,把多个表查询结果联合起来,查询者只需要直接从视图中获取数据,不必考虑数据来源于不同表中的差异。

数据库三范式

  • 1NF:数据库表中的字段都是单一属性,不可再分。这个单一属性由基本类型构成,包括整形,实数,字符型,逻辑型,日期型等。

  • 2NF:数据库表中不存在非关键字段对任意候选关键字段的部分依赖(部分依赖指的是存在组合关键字中的某些字段决定非关键字段的情况),也即所有非关键字段都完全依赖于任意一组候选关键字。

  • 3NF:数据表中不存在非关键字段对任一候选关键字段的传递依赖。传递依赖指的数据库中不存在如下依赖关系:关键字段->非关键字段X->非关键字段Y

SQL语句总结

  1. SELECT:将数据从数据库中的表中取出,SELECT "列名" FROM "表名"。

  2. DISTINCT:去重,在上述 SELECT 关键词后加上一个 DISTINCT 就可以去除选择出来的重复,从而完成求得这个表格中该字段有哪些不同的值的功能。语法为SELECT DISTINCT "列名" FROM "表名"。

  3. WHERE:这个关键词可以帮助我们选择性地获取数据,而不是全取出来。语法为SELECT "列名" FROM "表名" WHERE "条件"

  4. AND OR:上例中的 WHERE 指令可以被用来由表格中有条件地选取资料。这个条件可能是简单的,也可能是复杂的。复杂条件是由二或多个简单条件透过 AND 或是 OR 的连接而成。语法为:SELECT "列名" FROM "表名" WHERE "简单条件" {[AND|OR] "简单条件"}+

  5. IN:在SQL中,与 WHERE一起使用,我们事先已知道至少一个我们需要的值,而我们将这些知道的值都放入 IN 这个子句。语法为:SELECT "列名" FROM "表名" WHERE "列名" IN ('值一', '值二', ...)

  6. BETWEEN:IN 这个指令可以让我们依照一或数个不连续 (discrete)的值的限制之内获取数据,而 BETWEEN 则是让我们可以运用一个范围 (range)获取数据,语法为:
    SELECT "列名" FROM "表名" WHERE "列名" BETWEEN '值一' AND '值二'

  7. LIKE:LIKE 是另一个在 WHERE子句中会用到的指令。LIKE依据一个模式(pattern)来找出我们要的数据。语法为:
    SELECT "列名" FROM "表名" WHERE "列名" LIKE {模式}

  8. ORDER BY:排序指令。语法为:SELECT "列名" FROM "表名 [WHERE "条件"] ORDER BY "列名" [ASC, DESC]

  9. 函数: 函数允许我们能够对这些数字的型态存在的行或者列做运算,包括 AVG (平均)、COUNT (计数)、MAX (最大值)、MIN (最小值)、SUM (总合)。语法为:SELECT "函数名"("列名") FROM "表名"

  10. COUNT:这个关键词能够帮我我们统计有多少个数据被选出来,语法为:SELECT COUNT("列名") FROM "表名"

  11. GROUP BY:GROUP BY 语句用于结合合计函数,根据一个或多个列对结果集进行分组。语法为:SELECT "列1", SUM("列2") FROM "表名" GROUP BY "列1"

  12. HAVING:该关键词可以帮助我们对函数产生的值来设定条件。语法为:SELECT ";列1", SUM("列2") FROM "表格名" GROUP BY "列1" HAVING (函数条件)

  13. ALIAS:我们可以通过ALIAS为列名称和表名称指定别名,语法为:SELECT "表格别名"."列1" ";列1别名" FROM "表格名" "表格别名"

  • 实战,数据库结构如下

Student(S#,Sname,Sage,Ssex) 学生表
Course(C#,Cname,T#) 课程表
SC(S#,C#,score) 成绩表
Teacher(T#,Tname) 教师表

1. 查询“001”课程比“002”课程成绩高的所有学生的学号:

1
2
3
SELECT a.S# from (select s#,score from SC where C#='001') a,
(select s#,score from SC where C#='002') b
WHERE a.score > b.score and a.S#=b.S#;
2. 查询平均成绩大于60分的同学的学号和平均成绩:
1
2
3
SELECT S#, AVG(score)
FROM SC
GROUP by S# HAVING AVG(score) > 60;
3. 查询所有同学的学号、姓名、选课数、总成绩:
1
2
3
select Student.S#, Student.Sname, COUNT(SC.C#), SUM(score)
from Student left Outer join SC on Student.S#=SC.S#
GROUP by Student.S#, Sname;
4. 查询姓“李”的老师的个数:
1
2
3
select COUNT(distinct(Tname))
from Teacher
WHERE Tname LIKE '李%';
5. 查询没学过“叶平”老师课的同学的学号、姓名;
1
2
3
select Student.S#,Student.Sname
from Student
where S# not in (select distinct( SC.S#) from SC,Course,Teacher where SC.C#=Course.C# and Teacher.T#=Course.T# and Teacher.Tname=’叶平’);
6. 查询学过“001”并且也学过编号“002”课程的同学的学号、姓名:
1
2
3
select Student.S#,Student.Sname
from Student,SC
where Student.S#=SC.S# and SC.C#=’001′and exists( Select * from SC as SC_2 where SC_2.S#=SC.S# and SC_2.C#=’002′);
7. 查询学过“叶平”老师所教的所有课的同学的学号、姓名:
1
2
3
4
5
6
select S#,Sname
from Student
where S# in
(select S#
from SC ,Course ,Teacher
where SC.C#=Course.C# and Teacher.T#=Course.T# and Teacher.Tname=’叶平’ group by S# having count(SC.C#)=(select count(C#) from Course,Teacher where Teacher.T#=Course.T# and Tname=’叶平’));
8. 查询所有课程成绩小于60分的同学的学号、姓名:
1
2
3
select S#,Sname
from Student
where S# not in (select Student.S# from Student,SC where S.S#=SC.S# and score>60);
9. 查询没有学全所有课的同学的学号、姓名:
1
2
3
4
select Student.S#,Student.Sname
from Student,SC
where Student.S#=SC.S#
group by Student.S#,Student.Sname having count(C#) <(select count(C#) from Course);
10. 查询至少有一门课与学号为“1001”的同学所学相同的同学的学号和姓名:
1
2
3
select S#,Sname
from Student,SC
where Student.S#=SC.S# and C# in (select C# from SC where S#='1001');
11. 删除学习“叶平”老师课的SC表记录:
1
2
3
Delect SC
from course ,Teacher
where Course.C#=SC.C# and Course.T#= Teacher.T# and Tname='叶平';
12. 查询各科成绩最高和最低的分:以如下形式显示:课程ID,最高分,最低分
1
2
3
4
5
6
7
8
9
10
11
12
13
SELECT L.C# 课程ID,L.score 最高分,R.score 最低分
FROM SC L ,SC R
WHERE L.C# = R.C#
and
L.score = (SELECT MAX(IL.score)
FROM SC IL,Student IM
WHERE IL.C# = L.C# and IM.S#=IL.S#
GROUP BY IL.C#)
and
R.Score = (SELECT MIN(IR.score)
FROM SC IR
WHERE IR.C# = R.C#
GROUP BY IR.C# );
13. 查询学生平均成绩及其名次:
1
2
3
4
5
6
7
SELECT 1+(SELECT COUNT( distinct 平均成绩)
FROM (SELECT S#,AVG(score) 平均成绩
FROM SC
GROUP BY S# ) T1
WHERE 平均成绩 > T2.平均成绩) 名次, S# 学生学号,平均成绩
FROM (SELECT S#,AVG(score) 平均成绩 FROM SC GROUP BY S# ) T2
ORDER BY 平均成绩 desc;
14. 查询各科成绩前三名的记录:(不考虑成绩并列情况)
1
2
3
4
5
6
7
SELECT t1.S# as 学生ID,t1.C# as 课程ID,Score as 分数
FROM SC t1
WHERE score IN (SELECT TOP 3 score
FROM SC
WHERE t1.C#= C#
ORDER BY score DESC)
ORDER BY t1.C#;
15. 查询每门功成绩最好的前两名
1
2
3
4
5
6
7
SELECT t1.S# as 学生ID,t1.C# as 课程ID,Score as 分数
FROM SC t1
WHERE score IN (SELECT TOP 2 score
FROM SC
WHERE t1.C#= C#
ORDER BY score DESC )
ORDER BY t1.C#;

数据结构-两个栈实现一个队列和两个队列实现一个栈

发表于 2019-04-21 | 分类于 数据结构

两个栈实现一个队列

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
import java.util.Stack;
/**
* Created by cdx0312
* 2018/4/13
*/
public class TwoStackImpQueue {
private Stack<Integer> stack1 = new Stack<>();
private Stack<Integer> stack2 = new Stack<>();
public void push(int node) {
stack1.push(node);
}
public Integer pop() {
if (stack2.size() <= 0) {
while (stack1.size() > 0) {
stack2.push(stack1.pop());
}
}
if (stack2.isEmpty()) {
try {
throw new Exception("queue is empty");
} catch (Exception e){
e.printStackTrace();
}
}
return stack2.pop();
}
}

两个队列实现一个栈

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
import java.util.LinkedList;
import java.util.Queue;
/**
* Created by cdx0312
* 2018/4/13
*/
public class TwoListImpStack {
private Queue<Integer> list1 = new LinkedList<>();
private Queue<Integer> list2 = new LinkedList<>();
public void push(int node) {
if (list1.isEmpty() && list2.isEmpty()){
list1.add(node);
} else if (list1.isEmpty()) {
list2.add(node);
} else {
list2.add(node);
}
}
public Integer pop() {
if (list1.isEmpty() && list2.isEmpty()){
try {
throw new Exception("stack is Empty!");
} catch (Exception e) {
e.printStackTrace();
}
} else if (list1.isEmpty()) {
while (list2.size() > 1) {
list1.add(list2.poll());
}
return list2.poll();
} else {
while (list1.size() > 1) {
list2.add(list1.poll());
}
return list1.poll();
}
return null;
}
}

算法-BFS

发表于 2019-04-21 | 分类于 Java , algorithms

完美平方数

DP解法和图论解法,掌握DP的为准,参考图论解法。

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
package leetcode.BFSGraphic;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Queue;
/**
* Created by cdx0312
* 2018/4/8
*/
public class PerfectSquares_279 {
public int numSquares(int n) {
Queue<Pair> queue = new LinkedList<>();
queue.offer(new Pair(n, 0));
boolean[] visited = new boolean[n+1];
visited[n] = true;
while (!queue.isEmpty()) {
Pair pair = queue.poll();
int num = pair.num;
int step = pair.step;
if (num == 0)
return step;
for (int i = 1; num - i * i >= 0; i++) {
if (!visited[num-i*i]) {
queue.offer(new Pair(num - i * i, step + 1));
visited[num - i * i] = true;
}
}
}
return n;
}
/**
* num:当前数
* step:n到num需要的步数
*/
class Pair {
int num;
int step;
public Pair(int num, int step) {
this.num = num;
this.step = step;
}
}
}

WordLadder

注释很清楚了。

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
package leetcode.BFSGraphic;
import java.util.*;
/**
* Created by cdx0312
* 2018/4/8
*/
public class WordLadder_127 {
/*
beginSet开始存放beginword,然后将经过一次变更字母之后可以编程的wordList中的字符串
放入beginSet,直到出现变换成最终的字符串。
*/
public int ladderLength(String beginWord, String endWord, List<String> wordList) {
Set<String> dict = new HashSet<>(wordList), qs = new HashSet<>(), qe = new HashSet<>(), vis = new HashSet<>();
qs.add(beginWord);
if (dict.contains(endWord))
qe.add(endWord); // all transformed words must be in dict (including endWord)
for (int len = 2; !qs.isEmpty(); len++) {
Set<String> nq = new HashSet<>();
for (String w : qs) {
for (int j = 0; j < w.length(); j++) {
char[] ch = w.toCharArray();
for (char c = 'a'; c <= 'z'; c++) {
if (c == w.charAt(j))
continue; // beginWord and endWord not the same, so bypass itself
ch[j] = c;
String nb = String.valueOf(ch);
if (qe.contains(nb))
return len; // meet from two ends
if (dict.contains(nb) && vis.add(nb))
nq.add(nb); // not meet yet, vis is safe to use
}
}
}
qs = (nq.size() < qe.size()) ? nq : qe; // switch to small one to traverse from other end
qe = (qs == nq) ? qe : nq;
}
return 0;
}
}

多线程并发编程

发表于 2019-04-21 | 分类于 Java , concurrent programming

并发基础知识与核心知识

并发与高并发

  • 并发:同时拥有两个或者多个线程,如果程序在单核处理器上运行,多个线程将交替的换入或者换出内存,这些线程是同时存在的,每个线程处于执行过程中的某个状态,如果运行在多核处理器上,此时程序中的每个线程将被分到一个处理器核上,因此可以同时运行。
  • 高并发:high concurrency是互联网分布式系统架构设计中必须考虑的因素之一,它通常是指通过设计保证系统能够同时并行处理很多请求。

CPU多级缓存

CPU多级缓存

  • CPU的频率远远快与主存的速度,在处理器的时钟周期内,CPU需要等待主存,为了最大限度的利用CPU的性能,引入了Cache,环节CPU和内存之间速度的不匹配。
  • 缓存的意义:

    • 时间局部性:如果某个数据被访问,那么在不久的将来它很可能再次被访问

    • 空间局部性:如果某个数据被访问,那么与它相邻的数据很快也可能被访问

  • 缓存一致性-MESI:

    • 用于保证多个CPU cache之间缓存共享数据的一致性

      • M:修改,该缓存行只被缓存在缓存中,与CPU之间的数据是不同的,需要重新写入主存

      • E:独享,该缓存行只被缓存在缓存中,与CPU之间的数据一致

      • S:共享,该缓存行可能被多个CPU进行缓存

      • I:无效,有其他CPU修改了该缓存航

      • local read

      • local write

      • remote read

      • remote write

  • 乱序执行优化:处理器为提高运算速度而做出违背代码原有顺序的优化

  • Java内存模型

    • Heap:运行时数据区,可以动态分配内存大小,GC负责,存取速度相对较慢

    • Stack:存取速度较快,大小固定

    • 当两个线程同时调用同一个对象的同一个方法,两个线程则拥有这个对象的私有拷贝

      内存模型抽象图

    • 同步操作与规则:

      • 操作:

        • lock:作用于主内存的变量,把一个变量标识为一个线程独占状态

        • unlock:作用于主内存的变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。

        • read:作用于主内存的变量,把一个变量值从主内存传输到线程的工作内存中,以便随后的load使用

        • load:作用于工作内存的变量,把read操作从主内存中得到的变量值放入到工作内存的变量副本中

        • use:作用于工作内存的变量,将工作内存中的一个变量值传递给执行引擎

        • assign:作用于工作内存的变量,把一个从执行引擎接收到的值赋值给工作内存的变量

        • store:作用于工作内存的变量,把工作内存中的一个变量的值传送到主内存中,以便随后的写入操作

        • write:作用于主内存的变量,把store操作从工作内存的中一个变量的值传送到主内存的变量中

      • 规则:

        • 如果要把一个变量从主内存中复制到工作内存,就需要按顺序的执行read和load操作,如果把变量从工作内存中同步回主内存中,就要按顺序地执行store和write操作。但Java内存模型只要求上述操作必须按顺序执行,而没有保证必须是连续执行

        • 不允许read和load,store和write操作之一单独出现

        • 不允许一个线程丢弃它最近的assign操作,即变量在工作内存中改变了之后必须同步到主内存中

        • 不允许一个线程无原因的把数据从工作内存同步回主内存中

        • 一个新的变量只能在主内存中诞生,不允许在工作内存中直接使用一个未被初始化的变量。也就是对一个变量实施use和store操作之前,必须先执行过了assign和load操作

        • 一个变量在同一时刻只允许一条线程对其进行lock操作,但是咯擦卡操作可以被同一条线程重复多次执行,多次lock之后,只有执行相同次数的unlock操作,变量才会被解锁。

        • 如果对一个变量执行lock操作,将会清空工作内存中此变量的值,在执行引擎使用这个变量前需要重新执行load或assign操作初始化变量的值

        • 如果一个变量事先没有被lock操作锁定,则不允许对它执行unlock操作,也不允许unlock一个被其他线程锁定的变量

        • 对一个变量执行unlock操作之前,必须先把此变量同步到主内存中

  • 并发的优势

    • 速度:同时处理多个请求,相应更快,复杂的操作可以分成多个进程同时进行

    • 设计:程序设计在某些情况下会更简单,也可以有更多的选择

    • 资源利用:CPU能够在等待IO的时候做一些其他事情

  • 并发的风险:

    • 安全性:多个线程共享数据时可能会产生与预期不相符的结果

    • 活跃性:某个操作无法继续进行下去的时候,就会发生活跃性的问题,比如死锁问题,饥饿问题等

    • 性能:线程过多会使得CPU频繁切换线程,调度时间增多,同步机制,消耗过多的内存

并发的线程安全处理

并发模拟

  • PostMan

  • Apache Bench

  • JMeter

  • Semaphore、countdownLatch

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
public class ConcurrencyTest {
// 请求总数
public static int clientTotal = 5000;
// 线程总数
public static int threadTotal = 200;
public static int count = 0;
public static void main(String[] args) throws InterruptedException {
ExecutorService es = Executors.newCachedThreadPool();
final Semaphore semaphore = new Semaphore(threadTotal);
final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
for (int i = 0; i < clientTotal; i++) {
es.execute(() -> {
try {
semaphore.acquire();
add();
semaphore.release();
} catch (InterruptedException e) {
log.error("exception", e);
e.printStackTrace();
}
countDownLatch.countDown();
});
}
countDownLatch.await();
log.info("count:{}", count);
}
private static void add() {
count++;
}
}

线程安全性

当多个线程访问某个类时,不管运行时环境采用何种调度方式,或者这些进程将如何交替执行,并且在主调代码中不需要任何额外的同步或者协同,这个类都将表现出正确的行为,那么这个类时线程安全的。

  • 原子性:提供了互斥访问,同一个时刻只有一个线程对它进行操作

    • Atomic包:CAS完成原子性

      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
      @Slf4j
      @ThreadSafe
      public class CountExample2 {
      private static int clientTotal = 5000;
      private static int threadTotal = 10;
      private static AtomicInteger count = new AtomicInteger(0);
      public static void main(String[] args) throws InterruptedException {
      ExecutorService es = Executors.newCachedThreadPool();
      final Semaphore semaphore = new Semaphore(threadTotal);
      final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
      for (int i = 0; i < clientTotal; i++) {
      es.execute(() -> {
      try {
      semaphore.acquire();
      add();
      semaphore.release();
      } catch (InterruptedException e) {
      log.error("exception", e);
      }
      countDownLatch.countDown();
      });
      }
      countDownLatch.await();
      es.shutdown();
      log.info("count: {}", count);
      }
      private static void add() {
      count.incrementAndGet();
      }
      }
      //21:20:18.403 [main] INFO com.imooc.concurentcy.example.count.CountExample2 - count: 5000
      • AtomicXXX:CAS、Unsafe.compareAndSwapInt

      • AtomicLong、LongAddr

      • AtomicReference、AtomicReferenceFieldUpdater

      • AtomicStampReference:CAS的ABA问题

    • 锁

      • synchronized,关键字,依赖JVM,不可中断的锁,适合竞争不激烈,可读性好

        • 修饰代码块,作用于调用的对象

        • 修饰方法,整个方法,作用于对象

        • 修饰静态方法,整个静态方法,作用于所有对象

        • 修饰类,作用于所有对象

      • Lock:依赖特殊的CPU指令,可中断锁,多样化同步,适合竞争激烈时能维持常态

  • 可见性:一个线程对主内存的数据的修改可以及时的被其他线程观察到

    • 导致共享变量在线程间不可见的原因

      • 线程交叉执行

      • 重排序结合线程交叉执行

      • 共享变量更新后的值没有在工作内存与主内存间及时更新

    • JMM关于synchronized的规定:

      • 线程解锁前,必须把共享变量的值,刷新到主内存中

      • 线程加锁时,将清空工作内存中共享变量的值,从而使用共享变量时需要从主内存中重新读取最新的值

    • volatile,通过内存屏障和禁止重排序优化来实现:

      • 对volatile变量写操作时,会在写操作后加入一条store屏障指令,将本地内存中的共享变量值刷新到主内存

      • 对volatile变量读操作时,会在读操作前键入一条load屏障指令,从主内存中读取共享变量

  • 有序性:一个线程观察其他线程中的指令的执行顺序,由于指令重排序的存在,该观察结果一般杂乱无序

    • Java内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,但是会影响多线程并发执行的正确性

    • volatile,synchronized,Lock

    • Happenes-Before原则

      • 程序次序原则:一个程序内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作

      • 锁定规则:一个unlock操作线性发生于后面对同一个锁的Lock操作

      • volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作

      • 传递规则:如果操作A先行发生于操作B,操作B先行发生于操作C,操作A先与操作C

      • 线程启动规则:Thread对象的start方法先行发生于此线程的每一个动作

      • 线程中断规则:对线程的interrupt方法的调用先行发生于被中断线程的代码检测到的中断事件的发生

      • 线程终结规则:线程中所有的操作都先行发生于线程的终止检测

      • 对象终结规则:一个对象的初始化完成先行发生于finalize()方法的开始

安全发布对象

  1. 发布对象:使一个对象能够被当前范围之外的代码所使用

  2. 对象逸出:一种错误的发布,一个对象没有构造完成就被其他线程所见

  3. 安全发布对象

    • 在静态初始化函数中初始化一个对象的引用

    • 将对象的引用保存到volatile类型域或者AtomicReference对象中

    • 将对象的引用保存到某个正确构造对象的final类型域中

    • 将对象的引用保存到一个由锁保护的域中

线程安全策略

  1. 不可变对象–String类型

    • 条件

      • 对象创建以后其状态不能修改

      • 对象所有的域都是final类型

      • 对象时正确创建的,创建过程中this引用没有逸出

    • final关键字

      • 修饰类:不能被继承,String,Integer,Long

      • 修饰方法:锁定方法不能被继承类修改,早期效率更高

      • 修饰变量:

        • 基本数据类型变量,数值在初始化之后不可被修改

        • 引用类型变量,初始化之后不能再指向另外一个对象,对象里面的值时可以进行修改的

      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
      public class ImmutableExample1 {
      private final static Integer a = 1;
      private final static String b = "2";
      private final static Map<Integer, Integer> map = Maps.newHashMap();
      static {
      map.put(1,2);
      map.put(3,4);
      map.put(5,6);
      }
      public static void main(String[] args) {
      // a = 2;
      // b = "a";
      // map = Maps.newTreeMap();
      log.info("{}", map.get(1));
      map.put(1,4);
      log.info("{}", map.get(1));
      }
      private void test(final int a) {
      // a = 1;
      }
      }
      /**
      * 17:04:10.511 [main] INFO com.imooc.concurentcy.example.immutable.ImmutableExample1 - 2
      * 17:04:10.517 [main] INFO com.imooc.concurentcy.example.immutable.ImmutableExample1 - 4
      */
    • Collections.unmodifiableXXX:Collection, List, Set, Map…

      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
      public class ImmutableExample2 {
      private final static Integer a = 1;
      private final static String b = "2";
      private static Map<Integer, Integer> map = Maps.newHashMap();
      static {
      map.put(1,2);
      map.put(3,4);
      map.put(5,6);
      map = Collections.unmodifiableMap(map);
      }
      public static void main(String[] args) {
      // a = 2;
      // b = "a";
      // map = Maps.newTreeMap();
      log.info("{}", map.get(1));
      map.put(1,4);
      log.info("{}", map.get(1));
      }
      }
      /**
      * Exception in thread "main" java.lang.UnsupportedOperationException
      * 17:02:41.546 [main] INFO com.imooc.concurentcy.example.immutable.ImmutableExample2 - 2
      * at java.util.Collections$UnmodifiableMap.put(Collections.java:1457)
      * at com.imooc.concurentcy.example.immutable.ImmutableExample2.main(ImmutableExample2.java:34)
      */
    • Guava:ImmutableXXX:Collection, List, Set, Map…

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      public class ImmutableExample3 {
      private final static ImmutableList list = ImmutableList.of(1,2,3);
      private final static ImmutableSet set = ImmutableSet.copyOf(list);
      private final static ImmutableMap<Integer, Integer> map = ImmutableMap.of(1,2,3,4);
      private final static ImmutableMap<Integer, Integer> map2 = ImmutableMap.<Integer, Integer>builder().put(1,2)
      .put(3,4).put(5,6).build();
      public static void main(String[] args) {
      // list.add(4);
      // Exception in thread "main" java.lang.UnsupportedOperationException
      // at com.google.common.collect.ImmutableCollection.add(ImmutableCollection.java:222)
      // at com.imooc.concurentcy.example.immutable.ImmutableExample3.main(ImmutableExample3.java:26)
      map.put(6,7);
      map2.put(7,8);
      }
      }
  2. 线程封闭

    • Ad-hoc线程封闭:程序控制是实现

    • 堆栈封闭:局部变量,无并发问题

    • ThreadLocal线程封闭

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      public class RequestHolder {
      private final static ThreadLocal<Long> requesetHolder = new ThreadLocal<>();
      public static void add(Long id) {
      requesetHolder.set(id);
      }
      public static Long getId() {
      return requesetHolder.get();
      }
      public static void remove() {
      requesetHolder.remove();
      }
      }
  3. 线程不安全的类与写法

    • StringBuilder->StringBuffer

      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
      @Slf4j
      public class StringExample1 {
      // 请求总数
      public static int clientTotal = 5000;
      // 同时并发执行的线程数
      public static int threadTotal = 200;
      // public static StringBuilder sb = new StringBuilder();
      public static StringBuffer sb = new StringBuffer();
      public static void main(String[] args) throws Exception {
      ExecutorService executorService = Executors.newCachedThreadPool();
      final Semaphore semaphore = new Semaphore(threadTotal);
      final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
      for (int i = 0; i < clientTotal ; i++) {
      executorService.execute(() -> {
      try {
      semaphore.acquire();
      update();
      semaphore.release();
      } catch (Exception e) {
      log.error("exception", e);
      }
      countDownLatch.countDown();
      });
      }
      countDownLatch.await();
      executorService.shutdown();
      log.info("count:{}", sb.length());
      }
      private static void update() {
      sb.append(1);
      }
      }
    • SimpleDateFormate-JodaTime

    • ArrayList,HashSet,HashMap等集合类

  4. 同步容器

    • Vector,Stack

    • HashTable:key和value不能为null

    • Collections.synchronizedXXX(List, Set, Map)

  5. 并发容器

    • ArrayList->CopyOnWriteArrayList

      • 读写分离

      • 保证最终一致性

      • 通过另外开辟空间来解决并发冲突

    • HashSet,TreeSet->CopyOnWriteArraySet,ConcurrentSkipListSet

    • HashMap,TreeMap->ConcurrentHashMap,ConcurrentSkipListMap

    • JUC的实际构成:

J.U.C之AQS

AQS-AbstractQueuedSynchronizer

  • 使用Node实现FIFO队列,可以用于构建锁或者其他同步装置的基础框架

  • 利用一个int类型表示状态

  • 使用方法是继承

  • 子类通过继承并通过实现其方法管理其状态,也就是acquire方法和release方法

  • 可以同时实现排他锁和共享锁模式

CountDownLatch

Semaphore

CyclicBarrier

ReentrantLock

  1. ReentrantLock和synchronized的区别

    • 可重入性,都可以

    • 锁的实现,synchronized是依赖于JVM

    • 性能:优化之后差不多

    • 功能区别

  2. ReenTrantLock独有的功能:

    • 可以执行公平锁还是非公平锁

    • 提供一个Condition类,可以分组唤醒需要唤醒的线程

    • 可以终端等待锁的线程的机制

  3. StampLock–读,写,乐观读三种模式

Condition

FutureTask

  • Callable和Runnable接口对比

    • run,call

    • 返回值

  • Future接口–获取线程运行状态和结果

  • FutureTask

    • Runnable

    • Future

BlockingQueue

阻塞队列中的操作:

  • ArrayBlockingQueue

  • DelayQueue

  • LinkedBlockingQueue

  • PriorityBlockingQueue

  • SynchronousQueue

线程池

  • new Thread的弊端

    • 性能差

    • 线程缺乏统一管理,可能无限制的新建线程,相互竞争,有可能OOM

    • 缺少更多功能,

  • 线程池的好处

    • 重用存在的线程,减少对象的创建,销毁的开销,性能好

    • 可以有效的控制最大并发线程数,提高系统资源利用率,同时可以避免过多资源竞争,避免阻塞

    • 提供定期执行,定时执行,单线程,并发数控制等功能

  • ThreadPoolExecutor

    • corePoolSize

    • maximumPoolSize

    • workQueue-阻塞队列

    • keepAliveTime

    • unit

    • threadFactory

    • rejectHandler

  • 线程池的状态

    • running

    • shutdown

    • stop

    • tidying

    • terminated

    线程池状态

  • 提供的方法

    • execute

    • submit 有返回值 execute+future

    • shutdown

    • shutdownNow

    • getTaskCount:获取一致性和未执行的任务总数

    • getCompletedTaskCount

    • getPoolSize

    • getActiveCount

  • 四种线程池

    • Executors.newCachedThreadPool

    • Executors.newFixedThreadPool

    • Executors.newScheduledhreadPool

    • Executors.newSingleThreadExecutor

  • 合理配置

    • CPU密集型任务,需要尽量压榨CPU,参考值可以设置为CPU的核数+1

    • IO密集型任务,参考值可以设置为2*CPU的核数

并发扩展

死锁

  • 必要条件

    • 互斥条件

    • 请求和保持条件:对已经获取的资源不释放

    • 不剥夺条件

    • 环路等待条件:循环等待

  • 死锁实例

    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
    public class DeadLock implements Runnable{
    public int flag = 1;
    private static Object o1 = new Object();
    private static Object o2 = new Object();
    @Override
    public void run() {
    log.info("flag : {}", flag);
    if (flag == 1) {
    synchronized (o1) {
    try {
    Thread.sleep(500);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    synchronized (o2) {
    log.info("1");
    }
    }
    }
    if (flag == 0) {
    synchronized (o2) {
    try {
    Thread.sleep(500);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    synchronized (o1) {
    log.info("0");
    }
    }
    }
    }
    public static void main(String[] args) {
    DeadLock td1 = new DeadLock();
    DeadLock td2 = new DeadLock();
    td1.flag = 1;
    td2.flag = 0;
    //td1,td2都处于可执行状态,但JVM线程调度先执行哪个线程是不确定的。
    //td2的run()可能在td1的run()之前运行
    new Thread(td1).start();
    new Thread(td2).start();
    }
    }
  • 死锁避免方法:

    • 加锁顺序

    • 加锁时限,超时释放机制

    • 死锁检测

多线程并发的最佳实践

  • 使用本地变量

  • 使用不可变类

  • 最小化锁的作用于范围:s = 1 / (1 - a + a / n);

  • 使用线程池的Executor,而不是直接new Thread

  • 宁可使用同步也不要使用线程的wait和notify方法

  • 使用blockingQueue实现生产-消费模式

  • 使用并发集合而不是使用加了锁的同步集合

  • 使用Semaphore创建有界的访问

  • 宁可使用同步代码块,也不使用同步的方法

  • 避免使用静态变量

高并发处理思路及手段

扩容

  • 垂直扩容:提高系统部件能力,如加大内存

  • 水平扩容:增加更多系统成员来实现,如增加机器

  • 数据库扩容

    • 读操作扩展:memcache,redis,CDN缓存等

    • 写操作扩展:Cassandra,Hbase等

缓存

  • 缓存特征

    • 缓存命中率:命中数 / (命中数+未命中数)

    • 最大空间:缓存清空策略

      • FIFO:淘汰最先创建的数据

      • LFU:最少使用次数,清除使用次数最少的数据

      • LRU:淘汰最远使用的数据的时间戳-热点数据

      • 过期时间:过期时间最长的

      • Random:随机淘汰

  • 影响缓存命中率的因素

    • 业务场景和业务需求-读写

    • 缓存的设计(粒度和过期策略)

    • 缓存的容量和基础设施

    • 缓存节点故障等问题–一致性哈希算法

  • 缓存分类和应用场景:

    • 本地缓存:编程实现,Guava Cache

    • 分布式缓存:Memcached,Redis

  • Redis

    redis核心对象

  • 高并发场景下存在的问题

    • 缓存一致性

    • 缓存并发问题

    • 缓存穿透问题

      • 缓存空对象
    • 缓存雪崩现象

消息队列

  • 消息队列特性

    • 业务无关:只做消息分发

    • FIFO:先到先出

    • 容灾:节点的动态增删和消息的持久化

    • 性能:吞吐量提升,系统内部通信效率提高

  • 消息队列的好处

    • 业务解耦

    • 最终一致性

    • 广播

    • 错峰与流控

  • 队列实例

    • Kafka

      • 高性能,跨语言,分布式发布订阅消息队列系统

      • 快速持久化,O(1)

      • 高吞吐

      • 分布式系统

      • 支持Hadoop数据加载

    • RabbitMQ

应用拆分-解决单个服务器处理上限的问题

  • 应用拆分原则

    • 业务优先

    • 循序渐进

    • 兼顾技术:重构,分层

    • 可靠测试

  • 拆分过程中的关注点

    • 应用之间的通信:RPC or 消息队列 or …

    • 应用之间的数据库设计:每个应用单独库

    • 避免事务操作跨应用

  • 应用拆分

    • 服务化-Dubbo

      dubbo架构

    • 微服务-springcloud

      微服务架构

应用限流

  • 算法

    • 计数器法-滑动窗口的低精度情况

      计数器法

    • 滑动窗口

      滑动窗口

    • 漏桶算法-leaky Bucket

      漏桶算法

    • 令牌桶算法-token Bucket-允许流量一定程度的突发

      令牌桶算法

服务降级

  • 服务降级:设置默认返回,解决处理不了的请求。

    • 自动降级:超时,失败次数,故障,限流

    • 人工降级:秒杀,双11大促等

  • 服务熔断:过载保护

数据库分库分表

  • 数据库瓶颈

    • 单个库的数据量上限(1-2T)

    • 单个数据库服务压力过大,读写瓶颈:多个库

    • 单个表的数据量过大:分表

  • 数据库切库

    • 读写分离,降低主库的压力,多个从库通过负载均衡来调节
  • 数据库分表-单个表的数据量过大

    • 横向分表:同样结构的不同数据的表,id取模划分

    • 纵向分表:将本来可以在一个表的内容认为的划分为多个表,根据不同列的数据的活跃数量等进行分配

高可用

  • 任务调度系统分布式:elastic-job + zookeeper

  • 主备切换:Apache curator + zookeeper分布式锁实现

  • 监控报警机制

Zookeeper分布式专题

发表于 2019-04-21 | 分类于 分布式 , zookeeper

zookeeper简介

  • 中间件,提供协调服务
  • 作用于分布式系统,发挥其优势,可以为大数据服务
  • 支持java,提供java和c语言的客户端API

  • 特性:

    1. 一致性:数据一致性,数据按照顺序分批入库
    2. 原子性:事物要么成功,要么失败,不会局部化执行
    3. 单一视图:客户端连接集群中的任一个zk节点,数据都是一致的
    4. 可靠性:每次对zk的操作状态都会保存在服务端
    5. 实时性:客户端可以读取到zk服务端的最新数据

分布式系统

  • 多个计算机组成一个整体,一个整体一致对外并且处理同一请求
  • 内部的每台计算机都可以通信,restful/rpc
  • 客户端到服务端的一次请求到响应结束会经历多台计算机

zookeeper的安装

jdk安装

  1. 下载linux的jdk1.8,上传服务器
  2. 解压缩jdk,配置jdk
  3. 测试:java -version

    1
    2
    3
    4
    ubuntu@VM-0-8-ubuntu:~$ java -version
    java version "1.8.0_181"
    Java(TM) SE Runtime Environment (build 1.8.0_181-b13)
    Java HotSpot(TM) 64-Bit Server VM (build 25.181-b13, mixed mode)

zookeeper基本数据模型

  • zk的数据模型可以理解为linux的目录文件
  • 每一个节点称为znode,可以有子节点,也可以有数据
  • 每一个节点分为临时节点和永久节点,临时节点在客户端断开后消失
  • 每一个zk节点都有各自的版本号,可以通过命令行显示节点信息
  • 当节点的数据发生变化,该节点的版本号就会累加
  • 删除修改过的节点,版本号不匹配则会报错
  • 每个zk节点存储的数据不宜过大,几k即可
  • 节点可以设置权限acl,可以通过权限来限制用户的访问

基本操作

  • 客户端连接
  • 查看znode
  • 关闭连接

zk的作用

  • master节点选举,主节点挂了之后,从节点会接收工作,并且保证这个节点时唯一的,也就是所谓的首脑模式,从而保证集群的高可用。
  • 统一配置文件管理,只需要一台服务器,可以把相同的配置文件同步更新到其他的服务器,在云计算中使用的很多。
  • 发布与订阅,类似于MQ,dubbo发布者将数据存在znode,订阅者会读取这个数据。
  • 提供分布式锁,用于分布式环境中不同进程之间争夺资源,类似于多线程中的锁。
  • 集群管理,保证集群中数据的强一致性。

zk基本特性

  • ./zkCli.sh打开客户端

  • ls

  • ls2

  • get

  • stat

参数信息:

  • cZid:节点id
  • ctime:创建时间
  • mZid:修改节点的id
  • mTime
  • pZxid:子节点id
  • cversion:子节点version
  • dataVersion:当前节点数据的版本号
  • aclVersion:权限版本号
  • ephemeralOwner:
  • dataLength:
  • numChildren:子节点的个数

session的基本原理

  • 客户端和服务端之间的连接存在session
  • 每个session都可以设置一个超时时间
  • 心跳结束,session就过期
  • session过期,临时节点znode会被抛弃
  • 心跳机制:客户端向服务端的ping包请求

create命令

  • 创建持久化节点

  • 创建临时节点

  • 创建顺序节点

set命令

  • 根据版本号实现乐观锁

delete命令

  • 不设置版本号

  • 设置版本号需要版本号匹配才可以删除

watcher机制

  • 针对每一个节点的操作,都会有一个监督者,也就是watcher
  • 当监控的某一个对象(znode)发生了变化,则出发watcher事件
  • zk中的watcher是一次性的,触发后就会被立即销毁
  • 父节点,子节点,增删改查都能够触发其watcher
  • 针对不同类型的操作,触发的watcher事件也是不同的
    • 子节点创建事件
    • 子节点删除事件
    • 子节点数据变化事件

watcher命令行

  • watcher事件类型

    • 创建父节点触发:NodeCreated
    • 修改父节点数据触发:NodeDataChanged
    • 删除节点触发:NodeDeleted
    • ls为父节点设置watcher,创建子节点触发:NodeChildrenChanged
    • ls为父节点设置watcher,删除子节点触发:NodeChildrenChanged
    • ls为父节点设置watcher,修改子节点不触发任何事件
  • watcher使用场景

    • 统一资源配置

ACL(Acess Control Lists)权限控制

  • 针对节点可以设置相关读写等权限,目的为了保障数据安全性
  • 权限permissions可以指定不同的权限范围以及角色

ACL命令行

  • getAcl:获取某个节点的acl权限信息
  • setAcl:设置某个节点的acl权限信息
  • addauth:输入认证权限信息,注册时输入明文密码登录,但是在zk的系统中,密码以加密形式存在。
  • zk的acl通过【scheme:id:permissions】来构成权限列表
    • scheme:代表采用的某种权限机制
      • world:world下只有一个id,也就是只有一个用户anyone,任何人都可以访问该节点
      • auth:代表认证登录,需要注册用户有权限就可以,明文密码登录
      • digest:需要对密码加密才能访问
      • ip:当设置ip为指定的ip地址,此时限制ip进行访问
      • super:代表超级管理员,拥有所有的权限
    • id:代表允许访问的用户
    • permissions:权限组合字符串
      • crdwa:权限字符串
      • create:创建子节点
      • read:获取节点、子节点
      • write:设置节点数据
      • delete:删除子节点
      • admin:设置权限
  1. world:anyone:cdrwa

  1. auth:user:pwd:cdrwa
  2. digest:user:BASE64(SHA1(pwd)):cdrwa
  3. addauth digest user:pwd

zk集群

集群搭建的注意点

  • 配置文件myid 1/2/3对应server.1.2.3
  • 通过./zkCli.sh -server [ip]:[port]检测集群是否配置成功

一致性哈希及其Java实现

发表于 2019-04-21 | 分类于 Java , Consistent Hash

一致性哈希概念

  • 维基百科的定义

一致哈希 是一种特殊的哈希算法。在使用一致哈希算法后,哈希表槽位数(大小)的改变平均只需要对K/n 个关键字重新映射,其中K是关键字的数量,n是槽位数量。然而在传统的哈希表中,添加或删除一个槽位的几乎需要对所有关键字进行重新映射。

一致性哈希算法提出了动态变化的Cache环境中,判定哈希算法好坏的四个定义:

  1. 平衡性:哈希的结果尽可能分布到所有的缓存中,充分利用缓存空间。
  2. 单调性:举例说吧,本来有ABC三个缓存,数据1存储在B中,现在添加了D缓存进去,则数据1只会分布在B或者D中,不会分配到AC中。
  3. 分散性:分散性是同一个数据被映射到不同的缓存中,需要避免
  4. 负载:一个缓冲区被多个用户映射为不同的内容,需要降低缓冲的负载。

传统哈希算法

针对分布式场景,假定有1000W个数据项,100个存储接待你,一个数据的存储位置为该数据的Hash值对服务器数量取余,这样可以将数据尽可能的分散到各个服务器上。

Alt text

这种哈希方案的优点是每个节点的的数据量是相仿的,相对均衡,但是当在集群里面增加一台服务器,或者减少一台服务器的话,几乎所有的缓存都会失效,需要重新哈希。

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
/**
* Created by cdx0312
* 2018/4/18\
* 传统哈希方法
* 100000000个数据项,100个存储节点
*/
public class NormalHashMethod {
//数据数
public static final int NUMBER_OF_DATA = 10000000;
//节点数
public static final int NUMBER_OF_NODE = 100;
//节点中存放的数据个数
private static int[] record = new int[NUMBER_OF_NODE];
//将节点放入对应的服务器中
public static void hashProcess() {
for (int i = 0; i < NUMBER_OF_DATA; i++) {
int result = getHash(i+"");
record[result%100]++;
}
System.out.println("平均节点中的数据量: " + NUMBER_OF_DATA/NUMBER_OF_NODE);
System.out.println("最多的一个节点的数量: " + getMax(record));
System.out.println("最少的一个节点的数量: " + getMin(record));
}
//添加一个节点或者减少一个节点时,需要移动的节点数
public static void afterAddOrDeleteOneNode() {
int count = 0;
int count1 = 0;
//对所有数据进行hash,对新的服务器数量取余
for (int i = 0; i < NUMBER_OF_DATA; i++) {
int result = getHash(i+"");
if (result % NUMBER_OF_NODE != result % (NUMBER_OF_NODE + 1))
count++;
if (result % NUMBER_OF_NODE != result % (NUMBER_OF_NODE - 1))
count1++;
}
System.out.println("添加一个节点数据移动的数量: " + count);
System.out.println("减少一个节点数据移动的数量: " + count1);
}
//使用FNV1_32_HASH算法
private static int getHash(String str) {
final int p = 16777619;
int hash = (int) 2166136261L;
for (int i = 0; i < str.length(); i++)
hash = (hash ^ str.charAt(i)) * p;
hash += hash << 13;
hash ^= hash >> 7;
hash += hash << 3;
hash ^= hash >> 17;
hash += hash << 5;
// 如果算出来的值为负数则取其绝对值
if (hash < 0)
hash = Math.abs(hash);
return hash;
}
private static int getMax(int[] arr) {
int max = arr[0];
for (int x = 1; x < arr.length; x++) {
if (arr[x] > max)
max = arr[x];
}
return max;
}
private static int getMin(int[] arr) {
int min = arr[0];
for (int x = 1; x < arr.length; x++) {
if (arr[x] < min)
min = arr[x];
}
return min;
}
public static void main(String[] args) {
hashProcess();
afterAddOrDeleteOneNode();
}
}

上面的例子中是用普通哈希方法来做的,输出结果如下:

1
2
3
4
5
平均节点中的数据量: 100000
最多的一个节点的数量: 100653
最少的一个节点的数量: 99160
添加一个节点数据移动的数量: 9900492
减少一个节点数据移动的数量: 9899656

可以看出,一共1000万个数据,添加或者减少一个节点,进行重新哈希,则990万个数据需要改变存储位置。也就是说990万个缓存不能命中,实际场景中这明显是不能接受的,因此提出了一致性哈希来解决这个问题。

一致性哈希

原理

  • 唤醒hash空间:按照常用的hash算法来将对应的key哈希到一个具有2^32次方个桶的空间中,也就是0-2^32-1的数字空间中,将其收尾相连,形成一个环空间。

Hash(object1) = key1;
Hash(object2) = key2;
Hash(object3) = key3;
Hash(object4) = key4;

Alt text

  • 将数据通过一定的哈希算法处理后映射到环上

    Alt text

  • 将服务器通过哈希算法映射到环上,和数据的哈希算法是一样的,环上的数据,顺时针遇到的第一个服务器就是该数据的存储位置。假定有三个服务器节点。

Hash(NODE1) = KEY1;
Hash(NODE2) = KEY2;
Hash(NODE3) = KEY3;

Alt text

由于哈希环是不会变更的,因此一个数据可以快速的知道他的真正的存储位置。

服务器的删除与添加

不同于传统哈希求余方法,一致性哈希算法在增加或者删除服务器节点是,需要换位置的节点很少,性能很高。

  1. 删除服务器节点,如果NODE2出现故障,则从集群里面溢出,那么object3会迁移到Node3中,其他数据的位置不变。

    Alt text

  2. 添加服务器节点,向集群里面添加新的 首先将其映射到环中,通过顺时针迁移的规则,object2会迁移到NODE4中,其他对象不会发生改变。数据迁移的数量很小,很适合分布式集群。

    Alt text

  3. java示范

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
106
107
108
109
110
111
112
113
114
115
import java.util.Objects;
import java.util.SortedMap;
import java.util.TreeMap;
/**
* Created by cdx0312
* 2018/4/18\
* 一致性哈希方法
* 100000000个数据项,100个存储节点
*/
public class ConsistentHashMethod {
public static final int NUMBER_OF_DATA = 10000000;
public static final int NUMBER_OF_NODE = 100;
//记录服务器要添加的环上的位置,默认为均匀分布的
private static int[] servers = new int[NUMBER_OF_NODE];
//key-服务器的哈希值,value表示服务器的位置
private static SortedMap<Integer, Integer> sortedMap = new TreeMap<>();
//记录每个服务器节点放置的数据数
private static int[] record = new int[NUMBER_OF_NODE];
//初始化,将服务器放入环中
public static void init() {
int div = (Integer.MAX_VALUE) / NUMBER_OF_NODE;
for (int i = 0; i < 100; i++) {
servers[i] = i * div;
}
//将所有服务器放入SortedMap中
for (int i = 0; i < servers.length; i++) {
int hash = getHash(servers[i] + "");
sortedMap.put(hash, servers[i]);
}
}
public static void hashProcess() {
int div = 21474836;
for (int i = 0; i < NUMBER_OF_DATA; i++) {
int hash = getHash(i+"");
//得到大于该Hash值的所有Map
SortedMap<Integer, Integer> subMap = sortedMap.tailMap(hash);
//将节点放入对应的服务器中
if (subMap.isEmpty()) {
Integer index = sortedMap.firstKey();
record[sortedMap.get(index) / div]++;
} else {
Integer index = subMap.firstKey();
record[sortedMap.get(index)/div]++;
}
}
System.out.println("平均节点中的数据量: " + NUMBER_OF_DATA/NUMBER_OF_NODE);
System.out.println("最多的一个节点的数量: " + getMax(record));
System.out.println("最少的一个节点的数量: " + getMin(record));
}
public static void afterAddOneNode() {
int count = 0;
int div1 = (Integer.MAX_VALUE) / NUMBER_OF_NODE;
SortedMap<Integer, Integer> addNode = new TreeMap<>(sortedMap);
addNode.put(getHash("2333"), 2333);
for (int i = 0; i < NUMBER_OF_DATA; i++) {
int hash = getHash(i + "");
SortedMap<Integer, Integer> sub = sortedMap.tailMap(hash);
SortedMap<Integer, Integer> sub1 = addNode.tailMap(hash);
if (sub.isEmpty() && sub1.isEmpty() ) {
count++;
}
if (!sub.isEmpty() && !sub1.isEmpty() && !Objects.equals(sub.firstKey(), sub1.firstKey())) {
count++;
}
}
System.out.println("添加一个节点数据移动的数量: " + count);
}
//使用FNV1_32_HASH算法
private static int getHash(String str) {
final int p = 16777619;
int hash = (int) 2166136261L;
for (int i = 0; i < str.length(); i++)
hash = (hash ^ str.charAt(i)) * p;
hash += hash << 13;
hash ^= hash >> 7;
hash += hash << 3;
hash ^= hash >> 17;
hash += hash << 5;
// 如果算出来的值为负数则取其绝对值
if (hash < 0)
hash = Math.abs(hash);
return hash;
}
private static int getMax(int[] arr) {
int max = arr[0];
for (int x = 1; x < arr.length; x++) {
if (arr[x] > max)
max = arr[x];
}
return max;
}
private static int getMin(int[] arr) {
int min = arr[0];
for (int x = 1; x < arr.length; x++) {
if (arr[x] < min)
min = arr[x];
}
return min;
}
public static void main(String[] args) {
init();
hashProcess();
afterAddOneNode();
}
}

运行结果为:

1
2
3
4
平均节点中的数据量: 100000
最多的一个节点的数量: 479784
最少的一个节点的数量: 1367
添加一个节点数据移动的数量: 138303

可以看出添加一个节点之后,要修改的数据只有十万个,效率很高。

虚拟节点

上面可以明确看出,一致性哈希算法满足了单调性和负载均衡的特性以及一般hash算法的分散性,平衡性的体现通过虚拟节点来实现。哈希算法是不保证平衡的,如值部署了NODE1和NODE3的情况,object1存储到NODE1中,其他的都存到NODE3中,这样就非常不平衡了。因此引入了虚拟节点。

  • 虚拟节点:实际上是及其在哈希空间中的复制品,一个实际的节点对应了若干个虚拟节点,这样对应个数也成为复制个数,虚拟节点在哈希空间中以哈希值排列。新建两个复制节点之后可以看到节点分配变得很均衡。

    Alt text

其中涉及了哈希虚拟节点到实际节点的转换,其实只是一个简单的映射:

Alt text

而在实际的场景中,可以通过对真实节点进行编号来生成相应的虚拟节点。如真实节点的IP为192.168.10.11,则虚拟节点可以设置为192.168.10.11#1,192.168.10.11#2。

struts2总结(四)--异常处理和I18N

发表于 2019-04-21 | 分类于 Java , Struts2

6 声明式异常处理

通过配置的方式捕获指定类型异常,有ExceptionMappingInterceptor拦截器将异常信息(ExceptionHolder:exceptionStack, exception)压入栈顶,然后通过OGNL表达式在页面中获取异常。

  1. 在Actoin中进行异常映射

    • 只对当前Action对应类型的异常起作用,在struts2配置文件Action内部节点使用exception-mapping来映射异常,当发生SQLException时,会返回“error”信息,并跳转到error.jsp页面:
      1
      2
      3
      4
      5
      6
      <action name="*-*" class="com.bbs.action.{1}Action" method="{2}">
      <result>/admin/{1}-{2}.jsp</result>
      <result name="input">/admin/{1}-{2}.jsp</result>
      <exception-mapping exception="java.sql.SQLException" result="error" />
      <result name="error">/error.jsp</result>
      </action>
  2. 在package中进行全局异常映射

    • 针对所有Action对应类型的异常都起作用,可以通过OGNL在页面中获取异常信息需。
    • GLOBAL级别的声明式异常配置必须配置在所有的Action之前,同时,global-result节点需要配置在global-exception-mappings之前。
    • 当捕获到一个异常对象时,如果同时存在Action和全局两种级别的映射信息,则优先使用Action级别。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      <package name="default" extends="struts-default">
      <global-results>
      <result name="globleException">/error.jsp</result>
      </global-results>
      <global-exception-mappings>
      <exception-mapping result="globleException" exception="java.lang.Exception"/>
      </global-exception-mappings>
      </package>
  3. 使用继承共用异常映射,多个package可以通过extends来公用异常映射:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <package name="admin" namespace="/admin" extends="default" >
    <action name="index">
    <result>/admin/index.html</result>
    </action>
    <action name="*-*" class="com.action.{1}Action" method="{2}">
    <result>/admin/{1}-{2}.jsp</result>
    <result name="input">/admin/{1}-{2}.jsp</result>
    <result name="error">/error.jsp</result>
    </action>
    </package>
  4. Struts2中异常处理由拦截器实现(观察struts-default.xml)

    • 实际上Struts2的大多数功能都由拦截器实现

7 I18N

  1. I18N原理
    国际化,international。我们将程序中硬编码的文本转移到外部的资源文件(.properties)中期 ,针对每一种语言环境,编写一个资源文件。指定当前程序所用的语言环境,Java中的国际化处理机制能够自动找到对应的资源文件并读取内容。创建了两个资源文件,一个为bbs_en_US.properties,bbs_zh_CN.properties,需要注意的 是编码问题,IDEA中将File-Editor-Code Style-File Encodings-Transparent native-to-ascii conversion勾选上,就可以将中文自动转换成ASCII编码。

    • ResourceBundle和Locale的概念
    • ResourceBundle:按语言查找顺序
    • Locale命令是将当前语言环境或全部公共语言环境的信息输出到标准输出上
    • 资源文件:在Action所在的包或者任意一个父包中定义package.properties和package_language_COUTRY.properties资源文件
  2. Struts的资源文件

    • Action – Package – App级
    • 一般只用APP
    • struts.xml custom.i18n
    • 动态语言切换
    • request_locale=en_US
  3. 了解的不多,以后可以查教程
    I18N

struts2总结(六)--Struts2项目开发流程

发表于 2019-04-21 | 分类于 Java , Struts2
  1. 建立界面原型
  2. 建立struts.XHTML
    • 确定namespace
    • 确定package
    • 确定Result
    • 将界面原型页面进行修改,匹配现有设置
    • 测试
    • 做好规划
  3. 建立数据库
  4. 建立Model层
  5. 建立Service层
    • 使用JUnit进行单元测试
  6. 开始开发

struts2总结(五)--interceptor

发表于 2019-04-21 | 分类于 Java , Struts2

8 拦截器(interceptor)

8.1 Struts架构图

Alt text

8.2 Struts执行过程分析

Alt text

  • 拦截器:拦截器是struts2框架中提供的一种Java类
  • 作用:
    * 可以拦截访问Action的请求
    * 给这个Action加上新的丰富功能(上传,参数自动接收,类型自动转换等)需要配置之后,指明哪一个拦截器去拦截哪一个Action或者哪一些Action,这个拦截器才会去拦截我们的Action。
    
  • 拦截器工作原理:
    1. 有一个拦截器的类(框架的或者自定义的)
    2. 在配置文件中把这个拦截器类配置出来
    3. 指明这个拦截器要拦截哪一个或者哪一些action
    4. 客户端发送一个请求访问一个被拦截器拦截的Action
    5. 这个请求首先会被struts2 的filter所拦截,filter会检查这个请求是不是请求的Action,如果是,会再检查这个Action有没有被定义的拦截器所拦截,如果有,那么把这个请求交给拦截器去处理
    

8.3 Interceptor拦截器过程模拟

  • 定义一个Interceptor接口,有一个intercept方法。
1
2
3
public interface Inteceptor {
public void intercept(ActionInvocation actionInvocation);
}
  • 创建两个interceptor实现了interceptor接口:
1
2
3
4
5
6
7
8
public class FirstInterceptor implements Inteceptor{
@Override
public void intercept(ActionInvocation actionInvocation) {
System.out.println(1);
actionInvocation.invoke();
System.out.println(-1);
}
}
1
2
3
4
5
6
7
8
public class SecondInterceptor implements Inteceptor{
@Override
public void intercept(ActionInvocation actionInvocation) {
System.out.println(2);
actionInvocation.invoke();
System.out.println(-2);
}
}
  • 定义一个Action
1
2
3
4
5
6
7
8
public class SecondInterceptor implements Inteceptor{
@Override
public void intercept(ActionInvocation actionInvocation) {
System.out.println(2);
actionInvocation.invoke();
System.out.println(-2);
}
}
  • 定义ActionInvocation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import java.util.ArrayList;
import java.util.List;
public class ActionInvocation {
List<Inteceptor> inteceptors = new ArrayList<>();
int index = -1;
Action a = new Action();
public ActionInvocation() {
this.inteceptors.add(new FirstInterceptor());
this.inteceptors.add(new SecondInterceptor());
}
public void invoke() {
index++;
if (index >= this.inteceptors.size()) {
a.execute();
} else {
this.inteceptors.get(index).intercept(this);
}
}
}
  • 执行main文件:
1
2
3
4
5
public class Main {
public static void main(String[] args) {
new ActionInvocation().invoke();
}
}
  • 运行结果为:
1
2
3
4
5
1
2
execute
-2
-1

从而执行顺序为,首先main函数,创建ActionInvocation对象,初始化的时候讲两个Inteceptor加到interceptors这个列表中,调用其invoke()方法,当还有interceptor没有访问时,调用第一个interceptor的intercept方法,输出1,并调用ActionInvocation的invoke方法,此时会调用第二个Inteceptor的intercept方法,输出2,并调用ActionInvocation的invoke方法,此时不存在拦截器,执行Action输出execute,后面依次输出-2 -1, 完成这个模拟过程!

8.4 定义自己的拦截器

  • struts2框架已经写好了很多个拦截器,同时也把这些拦截器配置在配置文件里面struts-default.xml中,除此之外,我们可以写自己的拦截器

  • 首先实现一个Interceptor接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class MyInterceptor implements Interceptor {
public void destroy() {
// TODO Auto-generated method stub
}
public void init() {
// TODO Auto-generated method stub
}
public String intercept(ActionInvocation invocation) throws Exception {
long start = System.currentTimeMillis();
String r = invocation.invoke();
long end = System.currentTimeMillis();
System.out.println("action time = " + (end - start));
return r;
}
}
  • 然后在struts.xml文件中配置出这个拦截器:
1
2
3
4
5
6
7
8
9
10
11
12
<package name="test" namespace="/" extends="struts-default">
<interceptors>
<interceptor name="my" class="com.interceptor.MyInterceptor"></interceptor>
</interceptors>
<action name="test" class="com.action.TestAction">
<result>/test.jsp</result>
<interceptor-ref name="my"></interceptor-ref>
<interceptor-ref name="defaultStack"></interceptor-ref>
</action>
</package>

8.5 拦截器栈

当一个Action需要被多个拦截器拦截的时候,正常情况下我们需要在这个Action中去引用要使用的多个拦截器,但是我们可以使用一个拦截器栈去包含那几个拦截器,然后直接在Action中引用这个拦截器栈就可以。

  • 一个拦截器栈可以包含多个拦截器
  • 一个拦截器栈还可以包含其他拦截器栈
  • 定义拦截器栈都要在标签中
1
2
3
4
5
6
7
8
9
10
11
<interceptors>
<interceptor name="myInterceptor" class="com.interceptor.MyInterceptor"></interceptor>
<interceptor-stack name="myStack">
<!-- 这是我们自己定义的一个拦截器 -->
<interceptor-ref name="myInterceptor"></interceptor-ref>
<!-- 这是struts-default.xml文件中定义的一个拦截器 -->
<interceptor-ref name="params"></interceptor-ref>
<!-- 这是struts-default.xml文件中定义的一个拦截器栈 -->
<interceptor-ref name="basicStack"></interceptor-ref>
</interceptor-stack>
</interceptors>

8.6 默认拦截器栈/拦截器

在一个package中,我们可以把一个拦截器或者拦截器栈的声明为一个默认的拦截器/拦截器栈。以后这个package中所有的Action都会被这个默认的拦截器/拦截器栈所拦截

  • 我们写的任何Action都会被一个叫做defaultStack的拦截器栈所拦截,这个拦截器栈包含了十几个拦截器,这些拦截器给我们的Action提供了很多丰富的功能,因为我们写的所有的package都是直接或者间接继承了struts-default.xml文件中的一个名字叫struts-default的package, struts-default包中又把名字叫做defaultStack的拦截器栈配置成了一个默认的拦截器栈,我们的package继承了这个,所有的Action都会被defaultStack所拦截。
  • 但是如果我们指明了一个Action被我们所写的一个拦截器/拦截器栈所拦截,name这个Action就不会被defaultStack拦截了,所以我们需要在Action中主动的在声明这个Action被defaultStack所拦截,或者把defaultStack加入到我们自己定义的拦截器栈里面。
  • 也可以专门定义一个package专门定义拦截器,拦截器栈,其他package继承这个拦截器栈package:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!-- 在这个package中,我们只定义拦截器/拦截器栈 -->
<package name="MyInter" extends="struts-default" namespace="/">
<interceptors>
<interceptor name="myInterceptor" class="com.briup.web.interceptor.MyInterceptor"></interceptor>
<interceptor-stack name="myStack">
<interceptor-ref name="myInterceptor"></interceptor-ref>
<!-- 这是struts-default.xml文件中定义的一个拦截器栈 -->
<interceptor-ref name="defaultStack"></interceptor-ref>
</interceptor-stack>
</interceptors>
<!-- 声明默认拦截器/拦截器栈 -->
<!-- 当前包中所有的action都会被这个myStack所拦截器 -->
<!-- 继承了当前包的其他包里面的所有action也会被这个myStack所拦截器 -->
<default-interceptor-ref name="myStack"></default-interceptor-ref>
</package>

8.7 类型转换

类型转换:解析HTTP请求参数,将HTTP请求参数赋值给Action属性,当其类型不一致时进行的类型转换操作。struts2完成了String和基本类型的类型转换,只要Action属性有get set 方法。同时其还支持自定义的类型转换。

struts2类型转换时通过Params拦截器进行转换的;如果转换失败,则conversionError拦截器拦截该异常,并封装到fieldError中,放入ActionContext中。

8.7.1 基础类型转换:

testAction:

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
public class TestAction extends ActionSupport{
private String name;
private int age;
private Date d;
Set<String> interests;
Map<String, String> users;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Date getD() {
return d;
}
public void setD(Date d) {
this.d = d;
}
public Set<String> getInterests() {
return interests;
}
public void setInterests(Set<String> interests) {
this.interests = interests;
}
public Map<String, String> getUsers() {
return users;
}
public void setUsers(Map<String, String> users) {
this.users = users;
}
@Override
public String execute() throws Exception {
return super.execute();
}
}

test.jsp:

1
2
3
4
5
6
7
8
<body>
name:<s:property value="name"/><br/>
age:<s:property value="age"/><br/>
date:<s:property value="d"/><br/>
<s:date name="d" format="yyyy/MM/dd HH:mm:ss"/><br/>
<s:property value="interests"/><br/>
<s:property value="users"/><br/>
</body>

8.7.2自定义类型转换

自定义类型转换器方法:

继承DefaultTypeConverter,重写convertValue方法,这个方法的功能是完成双向转换,Sting数组转换到Action属性。函数模板为:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class MyPointConverter extends DefaultTypeConverter{
@Override
public Object convertValue(Object value, Class toType) {
if(toType == Point.class) {
Point p = new Point();
String[] strs = (String[])value;
String[] xy = strs[0].split(",");
p.x = Integer.parseInt(xy[0]);
p.y = Integer.parseInt(xy[1]);
return p;
}
if(toType == String.class) {
return value.toString();
}
return super.convertValue(value, toType);
}
}
继承StrutsTypeConverter
  • StrutsTypeConverter是DefaultTypeConverter的子类。在两个方向的类型转换上分别实现:

MyPointerConverter:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class MyPointConverter extends StrutsTypeConverter{
@Override
public Object convertFromString(Map map, String[] strings, Class aClass) {
Point point = new Point();
String[] strings1 = (String[])strings;
String[] xy = strings1[0].split(",");
point.x = Integer.parseInt(xy[0]);
point.y = Integer.parseInt(xy[1]);
return point;
}
@Override
public String convertToString(Map map, Object o) {
return null;
}
}

TestAction:

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
public class TestAction extends ActionSupport{
Point p;
List<Point> ps;
Map<String, Point> points;
public Point getP() {
return p;
}
public void setP(Point p) {
this.p = p;
}
public List<Point> getPs() {
return ps;
}
public void setPs(List<Point> ps) {
this.ps = ps;
}
public Map<String, Point> getPoints() {
return points;
}
public void setPoints(Map<String, Point> points) {
this.points = points;
}
@Override
public String execute() throws Exception {
return super.execute();
}
}

jsp:

1
2
3
4
5
<body>
<s:property value="p"/><br/>
<s:property value="ps"/><br/>
points:<s:property value="points"/><br/>
</body>
注册方式:
  • 第一种配置方法为在具备类型转换文件中配置,局部类型转换文件命名为ActionName-conversion.properties,位置放在特定的Action目录下,文件内容格式为:typeName=ConverClass仅针对特定的Action的特定属性有效
  • 第二种配置方法为全局类型转换文件中红配置,文件名为xwork-conversion.properties,在WEB-INF\classes下,文件内容格式为:attributeName=ConvertClass, 对某个类型都有效,比如对Point类型注册了类型转换器。
  • 注解

    8.8 拦截器和过滤器的比较

  • 相同点:
    • 都是一种Java类
    • 都能拦截客户端发给服务端的请求
    • 拦截到请求之后都可以做一些相应的处理,最后还可以把这个请求放行
    • 都需要实现各自相应的接口记忆在相应的配置文件中配置
  • 不同点:

    • 拦截器是Struts2框架中定义的,过滤器是web里面的对象,是J2EE标准定义的
    • 拦截器只会拦截访问Action的请求,过滤器可以拦截所有的请求
    • 拦截器定义在struts.xml文件中,过滤器定义在web.xml中
    • 拦截器对象的创建、调用、销毁时struts2框架负责的,过滤器对象的创建、调用、销毁时服务器负责的。

    我们自己定义的filter也可以拦截struts2框架中的Action,需要在web.xml文件中把我们自己的filter配置在struts2的filter上面才可以,因为web.xml文件中的filter配置的先后顺序控制filter起作用的顺序,如果struts的filter先拦截到访问action的请求后,不会把这个请求交给下面的filter,而是交给它内部的拦截器。如果我们自己的filter拦截到请求之后,还是会交给下一个filter,也就是交给struts2的filter

9 注解Annotation

9.1 注解使用方式

  1. 引入支持struts2框架注解开发的jar包 struts2-convention-plugin-2.3.4.1
  2. struts.xml <constant name="struts.convention.action.suffix" value="Action"/>
  3. Struts2使用注解开发需要遵循一定的规范:

    • Action要必须继承ActionSupport父类
    • Action所在的包名必须以Action结尾
    • package-info.java 在这里配置这个包下所有的类都可以用
    1
    2
    3
    4
    5
    6
    @Namespace("/")
    @ParentPackage("struts-default")
    @DefaultInterceptorRef("authStack")//authStack自定义的拦截器
    package com.briup.action.manager;
    import org.apache.struts2.convention.annotation.Namespace;
    import org.apache.struts2.convention.annotation.ParentPackage;

9.2 Action常用注解

  • Namespace Annotation

    • 通过在ActionClass上定义@Namespace(“/custom”)
    • 通过package0info.java定义
      1
      2
      3
      4
      @Namespace("/manager")
      @ParentPackage("default")
      @DefaultInterceptorRef("authStack")
      package com.example.actions;
  • Action Annotation

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    1. @Action(interceptorRefs={
    @InterceptorRef("validation"),
    @InterceptorRef("defaultStack")
    })
    2. chain
    @Action("foo")
    public String foo() {
    return "bar";
    }
    @Action("foo-bar")
    public String bar() {
    return SUCCESS;
    }
  • Result Annotation

    • 全局, 整个类可以访问
    • 局部, 某个方法可以访问
      1
      2
      3
      4
      5
      6
      7
      8
      9
      @Results({
      @Result(name="failure", location="fail.jsp")
      })
      public class HelloWorld extends ActionSupport {
      @Action(value="/different/url",results={@Result(name="success", location="http://struts.apache.org", type="redirect")} )
      public String execute() {
      return SUCCESS;
      }
      }

    参考:http://www.jianshu.com/p/a884504a8863

struts2总结(二)--Result和OGNL表达式

发表于 2019-04-21 | 分类于 Java , Struts2

4 Result

Result是Action执行结束之后返回的结果,result包含name和type属性,前者是返回的字符串,后者指定跳转的类型,默认为dispatcher。

4.1 Result常用的四中类型

  1. dispatcher, 从一个Action里面服务器内部跳转到一个页面中, r1–>r1.jsp
  2. chain, 从一个Action里面服务器内部跳转到另一个Action中,r2–>r2.jsp
  3. redirect, 从一个Action里面客户端重定向到一个页面中, r3->r1->r1.jsp
  4. redirectAction, 从一个Action里面客户端重定向到另一个Action中, r4->r2->r2.jsp
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    <struts>
    <constant name="struts.devMode" value="true" />
    <package name="resultTypes" namespace="/r" extends="struts-default">
    <action name="r1">
    <result type="dispatcher">/r1.jsp</result>
    </action>
    <action name="r2">
    <result type="redirect">/r2.jsp</result>
    </action>
    <action name="r3">
    <result type="chain">r1</result>
    </action>
    <action name="r4">
    <result type="redirectAction">r2</result>
    </action>
    </package>
    </struts>

4.2 全局结果集

1
2
3
<global-results>
<result name="mainpage">/main.jsp</result>
</global-results>

在其他任何Action中,不需要单独设置,所有返回mainpage字符串的Action会跳转到main.jsp页面。但是如果在某一个Action定义了mainpage字符串的跳转,全局跳转对这个acting失效。action结果集的优先级高于全局结果集。

4.3 动态结果(了解)

动态结果集表示在struts配置的时候,Action根据类型的不同,动态的决定返回的页面。首先需要在Action中定义两个成员变量,type和r, 其中type用于接收页面传入的参数,r用于保存Action传出的结果。
struts.xml为:

1
2
3
4
5
6
7
8
9
10
<struts>
<constant name="struts.devMode" value="true" />
<package name="user" namespace="/user" extends="struts-default">
<action name="user" class="com.bjsxt.struts2.user.action.UserAction">
<result>${r}</result>
</action>
</package>
</struts>

index.jsp:

1
2
3
4
5
6
7
8
9
<body>
动态结果
一定不要忘了为动态结果的保存值设置set get方法
<ol>
<li><a href="user/user?type=1">返回success</a></li>
<li><a href="user/user?type=2">返回error</a></li>
</ol>
</body>

UserAction:

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
public class UserAction extends ActionSupport {
private int type;
private String r;
public String getR() {
return r;
}
public void setR(String r) {
this.r = r;
}
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
@Override
public String execute() throws Exception {
if(type == 1) r="/user_success.jsp";
else if (type == 2) r="/user_error.jsp";
return "success";
}
}

4.4 传递参数

除了request,session,application来向页面传值之外,还可以通过ValueStack和ActionContext。

  • ValueStack是一个接口, ValueStack vs = context.getValueStack()将值从Action传到页面,ActionContext是一个类,ActionContext context = ActionContext.getContext()来传值,在页面中农需要<s:debug />标签来查看结果。
  • 当Action进行跳转的时候,struts2框架会自动的把这个Action对象本身分别放到ValueStack和ActionContext中去。然后struts2将这两个对象传递给页面,在页面中可以通过这两个对象来拿到之前放进去的值。也可以在Action中拿到这两个对象手动放值:
    1
    2
    3
    4
    5
    ActionContext ac = ActionContext.getContext();
    ValueStack vs = ac.getValueStack();
    ac.put("ac","ac");
    User user = new User();
    vs.push(user);

4.4.1 ValueStack

  1. 把一个对象放到值栈之后,文名从这个值栈中是拿不到这个对象的,但是文名可以直接拿到这个对象的属性及其属性值。
  2. 从值栈中拿值的时候,是从值栈的property name这一列来拿的,拿到的是property value之一列的值。
  3. 所以通过值栈来传递参数到页面需要将参数封装到一个对象中,将这个对象放到值栈中。
  4. 生命周期为request
  5. 创建一个新的值栈对象后,都会把这个对象放到ActionCon中去。

4.4.2 ActionContext

  1. 向ActionContext中放值的时候是通过k-v的形式存放的,String-Object键值对,取值同样是通过key拿到value。
  2. struts2框架默认向这个对象里面存放的对象很多,包括request,session,application,ValueStack,parameter等
  3. 每次请求都会创建一个新的ActionContext对象

4.4.3 取值方式

  1. jsp内置对象取值

    1
    2
    3
    <%=request.getAttribute("name") %><br>
    <%=session.getAttribute("name") %><br>
    <%=application.getAttribute("name") %><br>
  2. jstl标签+EL表达式

    1
    2
    3
    4
    5
    6
    ${requestScope.MyName }<br>
    ${requestScope.name } <br>
    ${sessionScope.MyName } <br>
    ${applicationScope.MyName } <br>
    ${MyName } <br>
    ${name } <br>
  3. struts2标签+OGNL表达式<s:property value="OGNL"

  • 从ValueStack中取值:这个value属性的值name,是一个OGNL表达式,表示取一个名字为name的property的值。从ValueStack中取值,如果存在重名的,默认取最上面的值。

    1
    2
    3
    4
    <s:property value="name"/>
    <s:property value="user"/>
    <s:property value="user.id"/>
    <s:property value="user.name"/>
  • 从ActionContext中取值 #

    1
    2
    3
    4
    5
    6
    <s:property value="#action.name"/><br>
    <s:property value="#msg"/><br>
    <s:property value="#user"/><br>
    <s:property value="#user.id"/><br>
    <s:property value="#user.name"/><br>
    <s:property value="#user.age"/><br>

5 OGNL表达式

OGNL:Object-Graph Navigation Language,图对象导航语言,是struts2中默认的表达式语言。

5.1 访问普通方法、属性、构造方法

1
2
3
4
5
6
7
8
9
<li>访问值栈中的action的普通属性: username = <s:property value="username"/> </li>
<li>访问值栈中对象的普通属性(get set方法):<s:property value="user.age"/> | <s:property value="user['age']"/> | <s:property value="user[\"age\"]"/> | wrong: <%--<s:property value="user[age]"/>--%></li>
<li>访问值栈中对象的普通属性(get set方法): <s:property value="cat.friend.name"/></li>
<li>访问值栈中对象的普通方法:<s:property value="password.length()"/></li>
<li>访问值栈中对象的普通方法:<s:property value="cat.miaomiao()" /></li>
<li>访问值栈中action的普通方法:<s:property value="m()" /></li>
<hr />
<li>访问普通类的构造方法:<s:property value="new com.struts2.ognl.User(8)"/></li>
<hr />

5.2 访问静态方法、属性

struts2默认的<constant name="struts.ognl.allowStaticMethodAccess" value="true"></constant>为false,需要配置成true才可以访问静态方法、属性

1
2
3
<li>访问静态方法:<s:property value="@com.struts2.ognl.S@s()"/></li>
<li>访问静态属性:<s:property value="@com.struts2.ognl.S@STR"/></li>
<li>访问Math类的静态方法:<s:property value="@@max(2,3)" /></li>

5.3 访问集合元素:

1
2
3
4
5
6
7
8
9
10
11
<li>访问List:<s:property value="users"/></li>
<li>访问List中某个元素:<s:property value="users[1]"/></li>
<li>访问List中元素某个属性的集合:<s:property value="users.{age}"/></li>
<li>访问List中元素某个属性的集合中的特定值:<s:property value="users.{age}[0]"/> | <s:property value="users[0].age"/></li>
<li>访问Set:<s:property value="dogs"/></li>
<li>访问Set中某个元素:<s:property value="dogs[1]"/></li>
<li>访问Map:<s:property value="dogMap"/></li>
<li>访问Map中某个元素:<s:property value="dogMap.dog101"/> | <s:property value="dogMap['dog101']"/> | <s:property value="dogMap[\"dog101\"]"/></li>
<li>访问Map中所有的key:<s:property value="dogMap.keys"/></li>
<li>访问Map中所有的value:<s:property value="dogMap.values"/></li>
<li>访问容器的大小:<s:property value="dogMap.size()"/> | <s:property value="users.size"/> </li>

5.4 投影和过滤

  • “?#”:过滤所有符合条件的集合
  • “^#”:过滤第一个符合条件的元素
  • “$#”:过滤最后一个符合条件的元素
  • 返回的是一个集合,可以使用索引取得集合中指定的元素。
1
2
3
4
<li>投影(过滤):<s:property value="users.{?#this.age==1}[0]"/></li>
<li>投影:<s:property value="users.{^#this.age>1}.{age}"/></li>
<li>投影:<s:property value="users.{$#this.age>1}.{age}"/></li>
<li>投影:<s:property value="users.{$#this.age>1}.{age} == null"/></li>

5.5 区分 %{} 和 ${}

  • %{}
    %{}的主要作用是告诉标签的处理类将它包含的字符串按照OGNL表达式处理,类似eval函数
  • ${}
    ${}的主要用于两方面:
    • 用于国际化资源文件中
    • 用于Struts2配置文件中
1…567…11
cdx

cdx

Be a better man!

110 日志
36 分类
31 标签
GitHub E-Mail
© 2020 cdx
由 Hexo 强力驱动
|
主题 — NexT.Mist v5.1.2