Java集合大全

上图为整理的集合类图关系,带对号标志的为线程安全类。

区别说明:

1、List Set Map Queue的区别
List: 有序,可以多个元素引用相同的对象
Set: 无序,不重复,不可以多个元素引用相同对象
Map: 使用键值对存储,两个key可以引用相同的对象,但是key不能重复

Queue:先入先出FIFO队列,可以重复对象

2、ArrayList 和LinkedList 区别
ArrayList: 底层使用数组,存、读效率高;插入、删除特定位置效率低,近似O(n)
LinkedList: 使用双向链表,插入、删除效率高O(1)


3、ArrayList 和 Vector 区别
Vector的所有方法都是同步的,ArrayList不同步
由于Vector类的方法都是使用了synchronized,所以效率比ArrayList低很多


4、HashMap 和 HashTable的区别
HashMap 是线程不安全的,HashTable线程安全
HashMap 效率高一点
HashMap 可以有null 值,HashTable 有Null会产生NullPointerException异常
HashTable 基本淘汰,需线程安全使用ConcurrentHashMap


5、HashMap 和 ConcurrentHashMap 区别
ConcurrentHashMap 把整个桶数组分割成很多个Segment,每个分段使用lock锁保护(1.8之后使用CAS算法)
HashMap键值对允许有null,ConcurrentHashMap反之。
CAS算法 compare and swap
无锁算法,CAS的语义是“我认为V的值应该为A,如果是,那么将V的值更新为B,否则不修改并告诉V的值实际为多少”


6、HashSet 和 HashMap 区别

7、HashSet 检查重复

当你把对象加入HashSet时,HashSet会先计算对象的hashcode值来判断对象加入的位置,同时也会与其他加入的对象的hashcode值作比较,如果没有相符的hashcode,HashSet会假设对象没有重复出现。但是如果发现有相同hashcode值的对象,这时会调用equals()方法来检查hashcode相等的对象是否真的相同。如果两者相同,HashSet就不会让加入操作成功。

8、hashCode() 和 equals()
如果两个对象相等,则hashcode一定也是相同的
两个对象相等,对两个equals方法返回true
两个对象有相同的hashcode值,它们也不一定是相等的
综上,equals方法被覆盖过,则hashCode方法也必须被覆盖
hashCode()的默认行为是对堆上的对象产生独特值。如果没有重写hashCode(),则该class的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)。


9.Vector和CopyOnWriteArrayList

二者均是线程安全的、基于数组实现的List

Vector是【绝对】线程安全的,CopyOnWriteArrayList只能保证读线程会读到【已完成】的写结果,但无法像Vector一样实现读操作的【等待写操作完成后再读最新值】的能力

CopyOnWriteArrayList读性能远高于Vector,并发线程越多优势越明显,适合读多写少的多线程场景

CopyOnWriteArrayList占用更多的内存空间10.TreeMap和ConcurrentSkipListMap

二者都能够提供有序的Entry集合

二者的性能相近,查找时间复杂度都是O(logN)

ConcurrentSkipListMap会占用更多的内存空间

10.TreeMap和ConcurrentSkipListMap

二者都能够提供有序的Entry集合

二者的性能相近,查找时间复杂度都是O(logN)

ConcurrentSkipListMap会占用更多的内存空间

ConcurrentSkipListMap是线程安全的,TreeMap不是

11.ConcurrentLinkedQueue、LinkedBlocklingQueue和ArrayBlockingQueue

ConcurrentLinkedQueue是非阻塞队列,其他两者为阻塞队列

三者都是线程安全的

LinkedBlocklingQueue是无界的,适合实现不限长度的队列, ArrayBlockingQueue适合实现定长的队列

12.PriorityQueue和PriorityBlockingQueue

这两种Queue并不是FIFO队列,而是根据元素的优先级进行排序,保证最小的元素最先出队,也可以在构造队列时传入Comparator实例,这样PriorityQueue就会按照Comparator实例的要求对元素进行排序。

PriorityQueue是非阻塞队列,也不是线程安全的,PriorityBlockingQueue是阻塞队列,同时也是线程安全的。 

参考:https://blog.csdn.net/jiwubai8849/article/details/82628859 参考:https://www.jianshu.com/p/b54f1df33f84

Java日期时间API系列26—–Jdk8中java.time包中的新的日期时间API类,YearMonth类的源码,转换和应用。

Java8中为年月新增了类YearMonth,可以用来表示卡片过期时间等问题。

1.YearMonth

默认格式为:2007-12

1.1 部分源码

*
 * @implSpec
 * This class is immutable and thread-safe.
 *
 * @since 1.8
 */
public final class YearMonth
        implements Temporal, TemporalAdjuster, Comparable, Serializable {

    /**
     * Serialization version.
     */
    private static final long serialVersionUID = 4183400860270640070L;
    /**
     * Parser.
     */
    private static final DateTimeFormatter PARSER = new DateTimeFormatterBuilder()
        .appendValue(YEAR, 4, 10, SignStyle.EXCEEDS_PAD)
        .appendLiteral('-')
        .appendValue(MONTH_OF_YEAR, 2)
        .toFormatter();

    /**
     * The year.
     */
    private final int year;
    /**
     * The month-of-year, not null.
     */
    private final int month;

通过源码可以看出使用final修饰YearMonth,YearMonth是线程安全类,同时实现了Temporal, TemporalAdjuster, Comparable, Serializable接口,有属性读取,设置和加减等功能。

 

 

 1.2 创建和基本使用

        YearMonth yearMonth = YearMonth.now();
        YearMonth yearMonth2 = YearMonth.of(2020, 4);
        
        System.out.println(yearMonth);
        System.out.println(yearMonth2);
        YearMonth yearMonth3 = YearMonth.parse("2020-05");
        System.out.println(yearMonth3);
        
        YearMonth yearMonth4 = yearMonth3.plusMonths(1);
        System.out.println(yearMonth4);

输出:

2020-03
2020-04
2020-05
2020-06

 

2.应用

2.1 转换

如Date转YearMonth,YearMonth转Date等
    /**
     * Date转YearMonth
     * @param date
     * @return
     */
    public static YearMonth toYearMonth(Date date){
        LocalDate localDate = toLocalDate(date);
        return YearMonth.of(localDate.getYear(), localDate.getMonthValue());
    }
    
    /**
     * LocalDateTime转YearMonth
     * @param localDateTime
     * @return
     */
    public static YearMonth toYearMonth(LocalDateTime localDateTime){
        LocalDate localDate = toLocalDate(localDateTime);
        return YearMonth.of(localDate.getYear(), localDate.getMonthValue());
    }
    
    /**
     * LocalDate转YearMonth
     * @param localDate
     * @return
     */
    public static YearMonth toYearMonth(LocalDate localDate){
        Objects.requireNonNull(localDate, "localDate");
        return YearMonth.of(localDate.getYear(), localDate.getMonthValue());
    }
    
    /**
     * Instant转YearMonth
     * @param instant
     * @return
     */
    public static YearMonth toYearMonth(Instant instant){
        LocalDate localDate = toLocalDate(instant);
        return YearMonth.of(localDate.getYear(), localDate.getMonthValue());
    }
    
    /**
     * ZonedDateTime转YearMonth
     * @param zonedDateTime
     * @return
     */
    public static YearMonth toYearMonth(ZonedDateTime zonedDateTime){
        LocalDate localDate = toLocalDate(zonedDateTime);
        return YearMonth.of(localDate.getYear(), localDate.getMonthValue());
    }

    /**
     * YearMonth转Date
     * 注意dayOfMonth范围:1到31之间,最大值根据月份确定特殊情况,如2月闰年29,非闰年28
     * 如果要转换为当月最后一天,可以使用下面方法:toDateEndOfMonth(YearMonth)
     * @param yearMonth
     * @param dayOfMonth
     * @return
     */
    public static Date toDate(YearMonth yearMonth, int dayOfMonth) {
        Objects.requireNonNull(yearMonth, "yearMonth");
        return toDate(yearMonth.atDay(dayOfMonth));
    }
    
    /**
     * YearMonth转Date,转换为当月第一天
     * @param yearMonth
     * @return
     */
    public static Date toDateStartOfMonth(YearMonth yearMonth) {
        return toDate(yearMonth, 1);
    }
    
    /**
     * YearMonth转Date,转换为当月最后一天
     * @param yearMonth
     * @return
     */
    public static Date toDateEndOfMonth(YearMonth yearMonth) {
        Objects.requireNonNull(yearMonth, "yearMonth");
        return toDate(yearMonth.atEndOfMonth());
    }

    /**
     * YearMonth转LocalDate
     * 注意dayOfMonth范围:1到31之间,最大值根据月份确定特殊情况,如2月闰年29,非闰年28
     * 如果要转换为当月最后一天,可以使用下面方法:toLocalDateEndOfMonth(YearMonth)
     * @param yearMonth
     * @param dayOfMonth
     * @return
     */
    public static LocalDate toLocalDate(YearMonth yearMonth, int dayOfMonth) {
        Objects.requireNonNull(yearMonth, "yearMonth");
        return yearMonth.atDay(dayOfMonth);
    }
    
    /**
     * YearMonth转LocalDate,转换为当月第一天
     * @param yearMonth
     * @return
     */
    public static LocalDate toLocalDateStartOfMonth(YearMonth yearMonth) {
        return toLocalDate(yearMonth, 1);
    }
    
    /**
     * YearMonth转LocalDate,转换为当月最后一天
     * @param yearMonth
     * @return
     */
    public static LocalDate toLocalDateEndOfMonth(YearMonth yearMonth) {
        Objects.requireNonNull(yearMonth, "yearMonth");
        return yearMonth.atEndOfMonth();
    }

 

测试代码:

    @Test
    public void yearMonthConverterTest(){
        System.out.println("===================yearMonthConverterTest=====================");
        Date date = new Date();
        System.out.println(date);
        YearMonth yearMonth = DateTimeConverterUtil.toYearMonth(date);
        System.out.println(yearMonth);
        
        Date date1 = DateTimeConverterUtil.toDate(yearMonth, 15);
        //转换为当月第一天
        Date date2 = DateTimeConverterUtil.toDateStartOfMonth(yearMonth);
        //转换为当月最后一天
        Date date3 = DateTimeConverterUtil.toDateEndOfMonth(yearMonth);
        System.out.println(date1);
        System.out.println(date2);
        System.out.println(date3);
        
        LocalDate LocalDate1 = DateTimeConverterUtil.toLocalDate(yearMonth, 15);
        //转换为当月第一天
        LocalDate LocalDate2 = DateTimeConverterUtil.toLocalDateStartOfMonth(yearMonth);
        //转换为当月最后一天
        LocalDate LocalDate3 = DateTimeConverterUtil.toLocalDateEndOfMonth(yearMonth);
        System.out.println(LocalDate1);
        System.out.println(LocalDate2);
        System.out.println(LocalDate3);
    }

 

输出:

===================yearMonthConverterTest=====================
Fri Mar 20 23:41:41 CST 2020
2020-03
Sun Mar 15 00:00:00 CST 2020
Sun Mar 01 00:00:00 CST 2020
Tue Mar 31 00:00:00 CST 2020
2020-03-15
2020-03-01
2020-03-31

 

2.2 计算

获取起始日期中间的日期列表,获取指定月份的日期列表,判断是否过期等等

    /**
     * 获取指定区间的时间列表,包含起始
     * @param startInclusive
     * @param endInclusive
     * @return
     */
    public static List getLocalDateTimeList(LocalDateTime startInclusive, LocalDateTime endInclusive){
        Objects.requireNonNull(startInclusive, "startInclusive");
        Objects.requireNonNull(endInclusive, "endInclusive");
        if(startInclusive.isAfter(endInclusive)){
            throw new DateTimeException("startInclusive must before or equal endInclusive!");
        }
        List localDateTimeList = new ArrayList();
        long days = betweenTotalDays(startInclusive, endInclusive)+1;
        for(long i=0; i){
            localDateTimeList.add(startInclusive.plusDays(i));
        }
        return localDateTimeList;
    }
    
    /**
     * 获取指定区间的时间列表,包含起始
     * @param startInclusive
     * @param endInclusive
     * @return
     */
    public static List getLocalDateList(LocalDate startInclusive, LocalDate endInclusive){
        return getLocalDateTimeList(DateTimeConverterUtil.toLocalDateTime(startInclusive),
                DateTimeConverterUtil.toLocalDateTime(endInclusive)).stream()
                        .map(localDateTime -> localDateTime.toLocalDate()).collect(Collectors.toList());
    }
    
    /**
     * 获取指定区间的时间列表,包含起始
     * @param startInclusive
     * @param endInclusive
     * @return
     */
    public static List getDateList(Date startInclusive, Date endInclusive){
        return getLocalDateTimeList(DateTimeConverterUtil.toLocalDateTime(startInclusive),
                DateTimeConverterUtil.toLocalDateTime(endInclusive)).stream()
                        .map(localDateTime -> DateTimeConverterUtil.toDate(localDateTime)).collect(Collectors.toList());
    }
    
    /**
     *  获取指定年月的所有日期列表
     * @param YearMonth
     * @return
     */
    public static List getLocalDateList(YearMonth yearMonth){
        Objects.requireNonNull(yearMonth, "yearMonth");
        List localDateList = new ArrayList();
        long days = yearMonth.lengthOfMonth();
        LocalDate localDate = DateTimeConverterUtil.toLocalDateStartOfMonth(yearMonth);
        for(long i=0; i){
            localDateList.add(plusDays(localDate, i));
        }
        return localDateList;
    }
    
    /**
     *  获取指定年月的所有日期列表
     * @param yearMonthStr yyyy-MM
     * @return
     */
    public static List getLocalDateList(String yearMonthStr){
        Objects.requireNonNull(yearMonthStr, "yearMonthStr");
        YearMonth yearMonth = YearMonth.parse(yearMonthStr);
        return getLocalDateList(yearMonth);
    }
    
    /**
     *  获取指定年月的所有日期列表
     * @param yearMonth
     * @return
     */
    public static List getLocalDateTimeList(YearMonth yearMonth){
        return getLocalDateList(yearMonth).stream()
                .map(localDate -> DateTimeConverterUtil.toLocalDateTime(localDate)).collect(Collectors.toList());
    }    
    
    /**
     *  获取指定年月的所有日期列表
     * @param yearMonthStr yyyy-MM
     * @return
     */
    public static List getLocalDateTimeList(String yearMonthStr){
        return getLocalDateList(yearMonthStr).stream()
                .map(localDate -> DateTimeConverterUtil.toLocalDateTime(localDate)).collect(Collectors.toList());
    }
    
    /**
     * 获取指定年月的所有日期列表
     * @param yearMonthStr yyyy-MM
     * @return
     */
    public static List getDateList(String yearMonthStr){
        return getLocalDateList(yearMonthStr).stream().map(localDate -> DateTimeConverterUtil.toDate(localDate))
                .collect(Collectors.toList());
    }
    
    /**
     * 判断是否过期,(输入年月小于当前年月)
     * @param yearMonth
     * @return
     */
    public static boolean isExpiry(YearMonth yearMonth){
        Objects.requireNonNull(yearMonth, "yearMonth");
        if(yearMonth.isBefore(YearMonth.now())){
            return true;
        }
        return false;
    }
    
    /**
     * 判断是否过期,(输入年月小于当前年月)
     * @param yearMonthStr yyyy-MM
     * @return
     */
    public static boolean isExpiry(String yearMonthStr){
        Objects.requireNonNull(yearMonthStr, "yearMonthStr");
        YearMonth yearMonth = YearMonth.parse(yearMonthStr);
        return isExpiry(yearMonth);
    }

 

测试代码:

    /**
     * yearMonth测试
     */
    @Test
    public void yearMonthTest(){
        //是否过期
        System.out.println(DateTimeCalculatorUtil.isExpiry("2020-03"));
        
        //获取指定年月的所有日期列表
        List dateList = DateTimeCalculatorUtil.getDateList("2020-03");
        dateList.stream().forEach(date->{
            System.out.println(DateTimeFormatterUtil.formatToDateStr(date));
        });
        
        System.out.println("========================");

        //获取指定区间的时间列表,包含起始
        List dateList2 = DateTimeCalculatorUtil.getDateList(dateList.get(0), dateList.get(dateList.size()-1));
        dateList2.stream().forEach(date->{
            System.out.println(DateTimeFormatterUtil.formatToDateStr(date));
        });
        
    }

 

输出:

false
2020-03-01
2020-03-02
2020-03-03
2020-03-04
2020-03-05
2020-03-06
2020-03-07
2020-03-08
2020-03-09
2020-03-10
2020-03-11
2020-03-12
2020-03-13
2020-03-14
2020-03-15
2020-03-16
2020-03-17
2020-03-18
2020-03-19
2020-03-20
2020-03-21
2020-03-22
2020-03-23
2020-03-24
2020-03-25
2020-03-26
2020-03-27
2020-03-28
2020-03-29
2020-03-30
2020-03-31
========================
2020-03-01
2020-03-02
2020-03-03
2020-03-04
2020-03-05
2020-03-06
2020-03-07
2020-03-08
2020-03-09
2020-03-10
2020-03-11
2020-03-12
2020-03-13
2020-03-14
2020-03-15
2020-03-16
2020-03-17
2020-03-18
2020-03-19
2020-03-20
2020-03-21
2020-03-22
2020-03-23
2020-03-24
2020-03-25
2020-03-26
2020-03-27
2020-03-28
2020-03-29
2020-03-30
2020-03-31

 

源代码地址:https://github.com/xkzhangsan/xk-time

 

Redis设置密码,保护数据安全

1.设置密码方法

在 redis.conf 的配置文件中修改参数 requirepass 的值为需要设置的密码,

保存配置文件后,重启Redis就可以。

建议 设置比较复杂和长的密码,防止被暴力破解。

 

2.redis-cli连接redis方式

(1)连接:redis-cli -p 6391

(2)认证:AUTH  密码

建议 连接后使用AUTH 密码认证 ,防止history查看执行命令历史,泄露密码。

 

3.修改默认端口号

建议不要使用默认端口6379,防止被调用到。

 

数据安全无小事!

 

Java SE 8 并发增强

1.原子值

java5开始,提供了一些原子操作的类,如AtomicInteger、AtomicLong等

这些类提供了诸如incrementAndGet这样的原子操作方法。

单数如果想进行复杂操作,则需要使用compareAndSet进行循环处理

do {

   // .. 计算

} while (!atomicLong.compareAndSet(old, new));

在java8中提供了updateAndGet和accumulateAndGet方法

atomicLong,updateAndGet(x -> Max.max(x, observed));

atomicLong.accumulateAndGet(observed, Math::max);

同时也提供了返回原始值的对应方法:getAndUpdate、getAndAccumulate

——————————————————

 

当大量线程访问同一个原始值时,由于乐观锁重试次数太多会导致性能下降

Java8为此提供了LongAdder和LongAccumulator解决该问题

其思想为将初始值变为多个中立元素,计算时不同线程可以对不同元素进行操作,最后再将操作结果合并。

 

例如:

LongAccumulator adder = new LongAccumulator (Long::sum, 0);

adder.accumulate(value);

此时在LongAccumulator 中包含多个中立元素a1,a2…aN.该例子下中立元素初始值都为零。当调用accumulate方法累加value时,这些变量的其中之一被更新为ai = ai op v。在这个实力中ai = ai + v;

而最后调用get方法的时候,结果为a1 op a2 op … aN. 在上述例子中为a1+a2+…aN

——————————————————

java8中还添加了StampedLock类实现乐观读

调用tryOptimisticRead方法时会获取一个印戳,当读取值并检测印戳有效,则可以使用这个值,否则会获得一个阻塞所有写锁的读锁

例:

复制代码
public class Vector {  
   private int size;  
   private Object[] elements;  
   private StampedLock lock = new StampedLock();  
   public Object get(int n) {  
        long stamp = lock.tryOptimisticRead();  
        Object[] currentElements = elements;  
        int currentSize = size;  
        if (!lock.validate(stamp)) { //  Someone else had a write lock  
             stamp = lock.readLock(); //  Get a pessimistic lock  
             currentElements = elements;  
             currentSize = size;  
             lock.unlockRead(stamp);  
        }  
    return n < currentSize ? currentElements[n] : null;  
  }  
...  
复制代码

2.ConcurrentHashMap改进

 

1. 更新值

concurrentHashMap在更新数值的时候虽然是线程安全的,但是在计算更新值的时候由于不能保证线程安全,更新的值可能是错误的。

一种补救措施是使用replace

例:

1 do {  
2    oldValue = map.get(word);  
3    newValue = oldValue == null ? 1 : oldValue + 1;  
4 } while (!map.replace(key, oldValue, newValue));  

此外还可以使用利用原子对象,例如CuncurrentHashMap

map.putIfAbsent(word, new LongAdder());  
map.get(word).increment();  

如果需要复杂计算,compute方法可以通过一个函数来计算新的值

map.compute(word, (k, v) -> v == null ? 1 : v + 1);  

xxxIfPresent和xxxIfAbsent方法分别表示已经存在值或者尚未存在值的情况下才进行操作

 

 

merge方法可以在key第一次加入时做一些特殊操作,第二个参数表示键尚未存在时的初始值

map.merge(word, 1L, (existingValue, newValue) -> existingValue + newValue);  

map.merge(word, 1L, Long::sum);  

--------------------------------------------------------------------------

 

2. 批量数据操作

・search会对每个键值对领用一个函数,直到函数返回非null,search会终止并返回函数结果

・reduce会通过提供的累计函数,将所有键值对组合起来

・foreach会对所有键值对应用一个函数

每个操作都有4个版本:

• operation Keys : 对键操作
• operation Values : 对值操作
• operation: 对键和值操作
• operation Entries : 对  Map.Entry 对象操作.

 

以search为例,有以下几个方法:

U searchKeys(long threshold, BiFunction f)
U searchValues(long threshold, BiFunction f)
U search(long threshold, BiFunction f)
U searchEntries(long threshold, BiFunction, ? extends U> f)

threshold为并行阀值,如果包含的元素数量超过阀值,操作会以并行方式执行,如果希望永远以单线程执行,请使用Long.MAX_VALUE

 

foreach和reduce方法除了上述形式外,还有另一种形式,可以提供一个转换器函数,首先会应用转换器函数,然后再将结果传递给消费者函数

map.forEach(threshold,  
(k, v) -> k + " -> " + v, // Transformer  
System.out::println); // Consumer  

 

Integer maxlength = map.reduceKeys(threshold,  
String::length, // Transformer  
Integer::max); // Accumulator  

对于int、long和double,reduce操作提供了专门的方法。以toXXX开头,需要将输入值转换为原始类型值,并指定一个默认值和累加器函数

long sum = map.reduceValuesToLong(threshold,  
Long::longValue, // Transformer to primitive type  
0, // Default value for empty map  
Long::sum); // Primitive type accumulator  

-----------------------------------------------------------------------------

 

3. Set视图

java8没有提供concurrenHashSet类,但是可以通过concurrentHashMap类通过虚假值获得一个映射

静态方法newKeySet会返回一个Set对象,它实际上是对ConcurrentHashMap对象的封装。

Set words = ConcurrentHashMap.newKeySet();  

如果你已经有一个映射,keySet方法会返回所有键的Set,但是你不能向这个set中添加元素,因为无法向map添加相应的值

 

于是,一个接收默认值的keySet方法可以解决上述问题,通过这个默认值向set中添加元素

Set words = map.keySet(1L);  
words.add("Java");  

key=java, value = 1L

 

 

 

3.并行数组操作

Arrays提供许多并行化操作

parallelSort可以进行并行排序,并且可以指定范围

1 String contents = new String(Files.readAllBytes(  
2 Paths.get("alice.txt")), StandardCharsets.UTF_8); // Read file into string  
3 String[] words = contents.split("[\\P{L}]+"); // Split along nonletters  
4 Arrays.parallelSort(words);  

 

 1 values.parallelSort(values.length / 2, values.length); // 对上半部排序  

 

parallelSetAll方法会根据提供的计算函数对参数values的每一个值进行计算并更新

Arrays.parallelSetAll(values, i -> i % 10);  
// Fills values with 0 1 2 3 4 5 6 7 8 9 0 1 2 . . . 

parallelPrefix将数组中每个元素替换为指定关联操作前缀的积累

假设array [1, 2, 3, 4, ...],执行完Arrays.parallelPrefix(values, (x, y) -> x * y)之后,array的结果为

[1, 1 × 2, 1 × 2 × 3, 1 × 2 × 3 × 4, ...]

 

4.可完成的Future

在过去,Future获取结果的方法为get,并且调用后会一直阻塞等待get返回结果

CompletableFuture提供了“当结果可用时,再按照提供的方式处理”的功能

1 CompletableFuture contents = readPage(url);  
2 CompletableFuture> links = contents.thenApply(Parser::getLinks);  

thenApply方法不会被阻塞,它会返回另一个Future对象,当第一个Future对象完成时,它的结果会发给getLinks方法

 

 

Future流水线类似Steam流水线,经过一个或多个转换过程,最后由一个终止操作结束。

如下代码可以启动一个流水线

1 CompletableFuture contents  
2 = CompletableFuture.supplyAsync(() -> blockingReadPage(url));  

另外还有一个runAsync方法,接收Runnable参数,返回CompletableFuture

 

 

接下来可以调用thenApply或者thenApplyAsync方法,在同一个线程或者另一个线程中运行另一个操作。

最终这些步骤执行完毕,需要将结果保存在某个地方,需要一个终止操作,例如:

1 CompletableFuture links  
2 = CompletableFuture.supplyAsync(() -> blockingReadPage(url))  
3 .thenApply(Parser::getLinks)  
4 .thenAccept(System.out::println);  

thenAccept方法接收一个Consumer接口(返回类型为void),理想情况下不需要调用Future的get方法

 

以下是一些常用方法:

thenCompose方法做的事就是,假设同时有两个调用链,T->CompletableFuture和U->CompletableFuture在连续调用的情况下,合并为T->CompletableFuture

类似的常用方法如下:

Java8并发教程:Threads和Executors

原文地址  原文作者:Benjamin Winterberg 译者:张坤

欢迎阅读我的Java8并发教程的第一部分。这份指南将会以简单易懂的代码示例来教给你如何在Java8中进行并发编程。这是一系列教程中的第一部分。在接下来的15分钟,你将会学会如何通过线程,任务(tasks)和 exector services来并行执行代码。

  • 第一部分:Threads和Executors
  • 第二部分:同步和锁

并发在Java5中首次被引入并在后续的版本中不断得到增强。在这篇文章中介绍的大部分概念同样适用于以前的Java版本。不过我的代码示例聚焦于Java8,大量使用lambda表达式和其他新特性。如果你对lambda表达式不属性,我推荐你首先阅读我的Java 8 教程

Threads 和 Runnables

所有的现代操作系统都通过进程和线程来支持并发。进程是通常彼此独立运行的程序的实例,比如,如果你启动了一个Java程序,操作系统产生一个新的进程,与其他程序一起并行执行。在这些进程的内部,我们使用线程并发执行代码,因此,我们可以最大限度的利用CPU可用的核心(core)。

Java从JDK1.0开始执行线程。在开始一个新的线程之前,你必须指定由这个线程执行的代码,通常称为task。这可以通过实现Runnable——一个定义了一个无返回值无参数的run()方法的函数接口,如下面的代码所示:

Runnable task = () -> {
    String threadName = Thread.currentThread().getName();
    System.out.println("Hello " + threadName);
};

task.run();

Thread thread = new Thread(task);
thread.start();

System.out.println("Done!");

因为Runnable是一个函数接口,所以我们利用lambda表达式将当前的线程名打印到控制台。首先,在开始一个线程前我们在主线程中直接运行runnable。

控制台输出的结果可能像下面这样:

Hello main
Hello Thread-0
Done!

或者这样:

Hello main
Done!
Hello Thread-0

由于我们不能预测这个runnable是在打印’done’前执行还是在之后执行。顺序是不确定的,因此在大的程序中编写并发程序是一个复杂的任务。

我们可以将线程休眠确定的时间。在这篇文章接下来的代码示例中我们可以通过这种方法来模拟长时间运行的任务。

Runnable runnable = () -> {
    try {
        String name = Thread.currentThread().getName();
        System.out.println("Foo " + name);
        TimeUnit.SECONDS.sleep(1);
        System.out.println("Bar " + name);
    }
    catch (InterruptedException e) {
        e.printStackTrace();
    }
};

Thread thread = new Thread(runnable);
thread.start();

当你运行上面的代码时,你会注意到在第一条打印语句和第二条打印语句之间存在一分钟的延迟。TimeUnit在处理单位时间时一个有用的枚举类。你可以通过调用Thread.sleep(1000)来达到同样的目的。

使用Thread类是很单调的且容易出错。由于并发API在2004年Java5发布的时候才被引入。这些API位于java.util.concurrent包下,包含很多处理并发编程的有用的类。自从这些并发API引入以来,在随后的新的Java版本发布过程中得到不断的增强,甚至Java8提供了新的类和方法来处理并发。

接下来,让我们走进并发API中最重要的一部——executor services。

Executors

并发API引入了ExecutorService作为一个在程序中直接使用Thread的高层次的替换方案。Executos支持运行异步任务,通常管理一个线程池,这样一来我们就不需要手动去创建新的线程。在不断地处理任务的过程中,线程池内部线程将会得到复用,因此,在我们可以使用一个executor service来运行和我们想在我们整个程序中执行的一样多的并发任务。

下面是使用executors的第一个代码示例:

ExecutorService executor = Executors.newSingleThreadExecutor();
executor.submit(() -> {
String threadName = Thread.currentThread().getName();
System.out.println("Hello " + threadName);

});

// => Hello pool-1-thread-1

Executors类提供了便利的工厂方法来创建不同类型的 executor services。在这个示例中我们使用了一个单线程线程池的 executor。

代码运行的结果类似于上一个示例,但是当运行代码时,你会注意到一个很大的差别:Java进程从没有停止!Executors必须显式的停止-否则它们将持续监听新的任务。

ExecutorService提供了两个方法来达到这个目的——shutdwon()会等待正在执行的任务执行完而shutdownNow()会终止所有正在执行的任务并立即关闭execuotr。

这是我喜欢的通常关闭executors的方式:

try {
    System.out.println("attempt to shutdown executor");
    executor.shutdown();
    executor.awaitTermination(5, TimeUnit.SECONDS);
    }
catch (InterruptedException e) {
    System.err.println("tasks interrupted");
}
finally {
    if (!executor.isTerminated()) {
        System.err.println("cancel non-finished tasks");
    }
    executor.shutdownNow();
    System.out.println("shutdown finished");
}

executor通过等待指定的时间让当前执行的任务终止来“温柔的”关闭executor。在等待最长5分钟的时间后,execuote最终会通过中断所有的正在执行的任务关闭。

Callables 和 Futures

除了Runnable,executor还支持另一种类型的任务——Callable。Callables也是类似于runnables的函数接口,不同之处在于,Callable返回一个值。

下面的lambda表达式定义了一个callable:在休眠一分钟后返回一个整数。

Callable task = () -> {
    try {
        TimeUnit.SECONDS.sleep(1);
        return 123;
    }
    catch (InterruptedException e) {
        throw new IllegalStateException("task interrupted", e);
    }
};

Callbale也可以像runnbales一样提交给 executor services。但是callables的结果怎么办?因为submit()不会等待任务完成,executor service不能直接返回callable的结果。不过,executor 可以返回一个Future类型的结果,它可以用来在稍后某个时间取出实际的结果。

ExecutorService executor = Executors.newFixedThreadPool(1);
Future future = executor.submit(task);

System.out.println("future done? " + future.isDone());

Integer result = future.get();

System.out.println("future done? " + future.isDone());
System.out.print("result: " + result);

在将callable提交给exector之后,我们先通过调用isDone()来检查这个future是否已经完成执行。我十分确定这会发生什么,因为在返回那个整数之前callable会休眠一分钟、

在调用get()方法时,当前线程会阻塞等待,直到callable在返回实际的结果123之前执行完成。现在future执行完毕,我们可以在控制台看到如下的结果:

future done? false
future done? true
result: 123

Future与底层的executor service紧密的结合在一起。记住,如果你关闭executor,所有的未中止的future都会抛出异常。

executor.shutdownNow();
future.get();

你可能注意到我们这次创建executor的方式与上一个例子稍有不同。我们使用newFixedThreadPool(1)来创建一个单线程线程池的 execuot service。 这等同于使用newSingleThreadExecutor不过使用第二种方式我们可以稍后通过简单的传入一个比1大的值来增加线程池的大小。

Timeouts

任何future.get()调用都会阻塞,然后等待直到callable中止。在最糟糕的情况下,一个callable持续运行——因此使你的程序将没有响应。我们可以简单的传入一个时长来避免这种情况。

ExecutorService executor = Executors.newFixedThreadPool(1);

    Future future = executor.submit(() -> {
    try {
        TimeUnit.SECONDS.sleep(2);
        return 123;
    }
    catch (InterruptedException e) {
        throw new IllegalStateException("task interrupted", e);
    }
});

    future.get(1, TimeUnit.SECONDS);

运行上面的代码将会产生一个TimeoutException

Exception in thread "main" java.util.concurrent.TimeoutException
    at java.util.concurrent.FutureTask.get(FutureTask.java:205)

你可能已经猜到为什么会抛出这个异常。我们指定的最长等待时间为1分钟,而这个callable在返回结果之前实际需要两分钟。

invokeAll

Executors支持通过invokeAll()一次批量提交多个callable。这个方法结果一个callable的集合,然后返回一个future的列表。

ExecutorService executor = Executors.newWorkStealingPool();

List> callables = Arrays.asList(
        () -> "task1",
        () -> "task2",
        () -> "task3");

executor.invokeAll(callables)
    .stream()
    .map(future -> {
        try {
            return future.get();
        }
        catch (Exception e) {
            throw new IllegalStateException(e);
        }
    })
    .forEach(System.out::println);

在这个例子中,我们利用Java8中的函数流(stream)来处理invokeAll()调用返回的所有future。我们首先将每一个future映射到它的返回值,然后将每个值打印到控制台。如果你还不属性stream,可以阅读我的Java8 Stream 教程

invokeAny

批量提交callable的另一种方式就是invokeAny(),它的工作方式与invokeAll()稍有不同。在等待future对象的过程中,这个方法将会阻塞直到第一个callable中止然后返回这一个callable的结果。

为了测试这种行为,我们利用这个帮助方法来模拟不同执行时间的callable。这个方法返回一个callable,这个callable休眠指定 的时间直到返回给定的结果。

Callable callable(String result, long sleepSeconds) {
    return () -> {
        TimeUnit.SECONDS.sleep(sleepSeconds);
        return result;
    };
}

 

我们利用这个方法创建一组callable,这些callable拥有不同的执行时间,从1分钟到3分钟。通过invokeAny()将这些callable提交给一个executor,返回最快的callable的字符串结果-在这个例子中为任务2:

ExecutorService executor = Executors.newWorkStealingPool();

List> callables = Arrays.asList(
callable("task1", 2),
callable("task2", 1),
callable("task3", 3));

String result = executor.invokeAny(callables);
System.out.println(result);

// => task2

上面这个例子又使用了另一种方式来创建executor——调用newWorkStealingPool()。这个工厂方法是Java8引入的,返回一个ForkJoinPool类型的 executor,它的工作方法与其他常见的execuotr稍有不同。与使用一个固定大小的线程池不同,ForkJoinPools使用一个并行因子数来创建,默认值为主机CPU的可用核心数。

ForkJoinPools 在Java7时引入,将会在这个系列后面的教程中详细讲解。让我们深入了解一下 scheduled executors 来结束本次教程。

Scheduled Executors

我们已经学习了如何在一个 executor 中提交和运行一次任务。为了持续的多次执行常见的任务,我们可以利用调度线程池。

ScheduledExecutorService支持任务调度,持续执行或者延迟一段时间后执行。

下面的实例,调度一个任务在延迟3分钟后执行:

ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);

Runnable task = () -> System.out.println("Scheduling: " + System.nanoTime());
ScheduledFuture future = executor.schedule(task, 3, TimeUnit.SECONDS);

TimeUnit.MILLISECONDS.sleep(1337);

long remainingDelay = future.getDelay(TimeUnit.MILLISECONDS);
System.out.printf("Remaining Delay: %sms", remainingDelay);

调度一个任务将会产生一个专门的future类型——ScheduleFuture,它除了提供了Future的所有方法之外,他还提供了getDelay()方法来获得剩余的延迟。在延迟消逝后,任务将会并发执行。

为了调度任务持续的执行,executors 提供了两个方法scheduleAtFixedRate()scheduleWithFixedDelay()。第一个方法用来以固定频率来执行一个任务,比如,下面这个示例中,每分钟一次:

ScheduledExecutorService executor =     Executors.newScheduledThreadPool(1);

Runnable task = () -> System.out.println("Scheduling: " + System.nanoTime());

int initialDelay = 0;
int period = 1;
executor.scheduleAtFixedRate(task, initialDelay, period, TimeUnit.SECONDS);

另外,这个方法还接收一个初始化延迟,用来指定这个任务首次被执行等待的时长。

请记住:scheduleAtFixedRate()并不考虑任务的实际用时。所以,如果你指定了一个period为1分钟而任务需要执行2分钟,那么线程池为了性能会更快的执行。

在这种情况下,你应该考虑使用scheduleWithFixedDelay()。这个方法的工作方式与上我们上面描述的类似。不同之处在于等待时间 period 的应用是在一次任务的结束和下一个任务的开始之间。例如:

ScheduledExecutorService executor =         Executors.newScheduledThreadPool(1);

Runnable task = () -> {
    try {
        TimeUnit.SECONDS.sleep(2);
        System.out.println("Scheduling: " + System.nanoTime());
    }
    catch (InterruptedException e) {
        System.err.println("task interrupted");
    }
};

executor.scheduleWithFixedDelay(task, 0, 1, TimeUnit.SECONDS);

这个例子调度了一个任务,并在一次执行的结束和下一次执行的开始之间设置了一个1分钟的固定延迟。初始化延迟为0,任务执行时间为0。所以我们分别在0s,3s,6s,9s等间隔处结束一次执行。如你所见,scheduleWithFixedDelay()在你不能预测调度任务的执行时长时是很有用的。

这是并发系列教程的第以部分。我推荐你亲手实践一下上面的代码示例。你可以从 Github 上找到这篇文章中所有的代码示例,所以欢迎你fork这个repo,给我星星

MySQL5.7增量备份恢复全实战

一. 简介

1. 增量备份

  增量备份是指在一次全备份或上一次增量备份后,以后每次的备份只需备份与前一次相比增加或者被修改的文件。这就意味着,第一次增量
备份的对象是进行全备后所产生的增加和修改的文件;第二次增量备份的对象是进行第一次增量备份后所产生的增加和修改的文件,如此类推。
  这种备份方式最显著的优点就是:没有重复的备份数据,因此备份的数据量不大,备份所需的时间很短。但增量备份的数据恢复是比较麻烦的。
必须具有上一次全备份和所有增量备份(一旦丢失或损坏其中的一个增量,就会造成恢复的失败),并且它们必须沿着从全备份到依次增量备份
的时间顺序逐个恢复,因此这就极大地延长了恢复时间。
  假如我们有一个数据库,有20G的数据,每天会增加10M的数据,数据库每天都要全量备份一次,这样的话服务器的压力比较大,因此我们只
需要备份增加的这部分数据,这样减少服务器的负担。

2. binlog简介

  binlog日志由配置文件的log-bin参数来启用,MySQL服务器将在指定目录下创建两个文件XXX-bin.001和xxx-bin.index,若配置选项
没有给出文件名,Mysql将使用主机名称命名这两个文件,其中.index文件包含一份全体日志文件的清单。
  Mysql会把用户对所有数据库的内容和结构的修改情况记入XXX-bin.n文件,而不会记录 SELECT和没有实际更新的UPDATE语句。
  当MySQL数据库停止或重启时,服务器会把日志文件记入下一个日志文件,Mysql会在重启时生成一个新的binlog日志文件,文件序号递
增,此外,如果日志文件超过max_binlog_size系统变量配置的上限时,也会生成新的日志文件。

二 binlog操作总结

2.1 开启binlog日志

修改 MySQL 的配置文件my.cnf 如下:
[mysqld] 
log-bin=/MySQL/my3306/log/binlog/binlog  
binlog_format = row 

#其中 log_bin若不显示指定存储目录,则默认存储在mysql的datadir参数指定的目录下
#binlog_format的几种格式:(STATEMENT,ROW和MIXED):
STATEMENT:基于SQL语句的复制(statement-based replication, SBR)    
ROW:基于行的复制(row-based replication, RBR)    
MIXED:混合模式复制(mixed-based replication, MBR)

#启动后会产生mysql-bin.*这样的文件,每启动一次,就会增加一个或者多个.


[root@localhost binlog]# cd /MySQL/my3306/log/binlog
[root@localhost binlog]# ll
total 28
-rw-r-----. 1 mysql mysql 177 Jun  6 00:03 binlog.000001
-rw-r-----. 1 mysql mysql 177 Jun  6 00:03 binlog.000002
-rw-r-----. 1 mysql mysql 981 Jun  6 00:07 binlog.000003
-rw-r-----. 1 mysql mysql 177 Jun  6 00:07 binlog.000004
-rw-r-----. 1 mysql mysql 177 Jun  7 01:39 binlog.000005
-rw-r-----. 1 mysql mysql 154 Jun  7 02:47 binlog.000006
-rw-r-----. 1 mysql mysql 234 Jun  7 02:47 binlog.index

2.2查看binlog相关参数

mysql> show variables like 'log_bin%';
+---------------------------------+---------------------------------------+
| Variable_name                   | Value                                 |
+---------------------------------+---------------------------------------+
| log_bin                         | ON                                    |
| log_bin_basename                | /MySQL/my3306/log/binlog/binlog       |
| log_bin_index                   | /MySQL/my3306/log/binlog/binlog.index |
| log_bin_trust_function_creators | OFF                                   |
| log_bin_use_v1_row_events       | OFF                                   |
+---------------------------------+---------------------------------------+

可以看到MySQL5.7中,log_bin参数如果指定了目录和名称,则被拆分为三个参数:log_bin,log_bin_basename,log_bin_index
分别对应 binlog是否开启,binlog名.index名

2.3 查看binlog日志内容

[root@localhost binlog]# mysqlbinlog /MySQL/my3306/log/binlog/binlog.000001
/*!50530 SET @@SESSION.PSEUDO_SLAVE_MODE=1*/;
/*!50003 SET @OLD_COMPLETION_TYPE=@@COMPLETION_TYPE,COMPLETION_TYPE=0*/;
DELIMITER /*!*/;
# at 4
#180606  0:03:38 server id 101  end_log_pos 123 CRC32 0xe534323e        Start: binlog v 4, server v 5.7.22-log created 180606  0:03:38 at startup
ROLLBACK/*!*/;
BINLOG '
Gl0XWw9lAAAAdwAAAHsAAAAAAAQANS43LjIyLWxvZwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAaXRdbEzgNAAgAEgAEBAQEEgAAXwAEGggAAAAICAgCAAAACgoKKioAEjQA
AT4yNOU=
'/*!*/;
# at 123
#180606  0:03:38 server id 101  end_log_pos 154 CRC32 0x9c28789d        Previous-GTIDs
# [empty]
# at 154
#180606  0:03:39 server id 101  end_log_pos 177 CRC32 0xac9e5d49        Stop
SET @@SESSION.GTID_NEXT= 'AUTOMATIC' /* added by mysqlbinlog */ /*!*/;
DELIMITER ;
# End of log file
/*!50003 SET COMPLETION_TYPE=@OLD_COMPLETION_TYPE*/;
/*!50530 SET @@SESSION.PSEUDO_SLAVE_MODE=0*/;

2.4 一些常用操作

mysql> show master logs;  #查看数据库所有日志文件。 
mysql> show binlog events \g;  #查看当前使用的binlog文件信息。 
mysql> show binlog events in 'binlog.000001';  #查看指定的binlog文件信息。 
mysql> flush logs;  #将内存中log日志写磁盘,保存在当前binlog文件中,并产生一个新的binlog日志文件。 
mysql> flush logs; reset master;  #删除所有二进制日志,并重新(binlog.000001)开始记录。

三 MySQL备份实例(全备 + 基于 binlog的增备)

本环境基于CentOS7.2+MySQL5.7

3.1 查看当前数据库binlog文件

mysql> show master logs;
+---------------+-----------+
| Log_name      | File_size |
+---------------+-----------+
| binlog.000001 |       177 |
| binlog.000002 |       177 |
| binlog.000003 |       981 |
| binlog.000004 |       177 |
| binlog.000005 |       177 |
| binlog.000006 |       154 |
+---------------+-----------+


[root@localhost binlog]# cd /MySQL/my3306/log/binlog/
[root@localhost binlog]# ll -h
total 28K
-rw-r-----. 1 mysql mysql 177 Jun  6 00:03 binlog.000001
-rw-r-----. 1 mysql mysql 177 Jun  6 00:03 binlog.000002
-rw-r-----. 1 mysql mysql 981 Jun  6 00:07 binlog.000003
-rw-r-----. 1 mysql mysql 177 Jun  6 00:07 binlog.000004
-rw-r-----. 1 mysql mysql 177 Jun  7 01:39 binlog.000005
-rw-r-----. 1 mysql mysql 154 Jun  7 02:47 binlog.000006
-rw-r-----. 1 mysql mysql 234 Jun  7 02:47 binlog.index

3.2模拟数据

mysql> create database test_backup;  
mysql> use test_backup 
mysql> create table t_test (c1 int(10), c2 varchar(20)) engine=innodb; 
mysql>  insert into t_test values (1, 'test1'),(2, 'test2'),(3, 'test3'),(4, 'test4'), 
        (5, 'test5'),(6, 'test6'),(7, 'test7'),(8, 'test8'),(9, 'test9'),(10, 'test10'); 

3.3 将全量数据进行备份

[root@localhost binlog]# mysqldump -uroot -proot  --socket=/MySQL/my3306/run/mysql.sock --port=3306 --single-transaction --master-data=2 test_backup >/tmp/test_backup20180611.sql 
 
#记录备份的日志位置,将来作为增量还原的起点
[root@localhost binlog]# cat /tmp/test_backup20180611.sql|grep "CHANGE MASTER" 
-- CHANGE MASTER TO MASTER_LOG_FILE='binlog.000006', MASTER_LOG_POS=996;

3.4 准备第一份增量数据

mysql> use test_backup; 
mysql> create table increment (c1 int(10), c2 varchar(20)) engine=innodb; 
mysql> insert into increment values (11, 'increment1'),(12, 'increment2'),(13, 'increment3'),(14, 'increment4'),(15, 'increment5'); 

3.5 将第一份增量数据进行备份

#将日志刷到当前的binlog文件中,也就是binlog.000006,数据库再有新的数据更新会记录在新的binlog(binlog.000007)里面.
mysql> flush logs;  

[root@localhost binlog]# ll
total 32
-rw-r-----. 1 mysql mysql  177 Jun  6 00:03 binlog.000001
-rw-r-----. 1 mysql mysql  177 Jun  6 00:03 binlog.000002
-rw-r-----. 1 mysql mysql  981 Jun  6 00:07 binlog.000003
-rw-r-----. 1 mysql mysql  177 Jun  6 00:07 binlog.000004
-rw-r-----. 1 mysql mysql  177 Jun  7 01:39 binlog.000005
-rw-r-----. 1 mysql mysql 1658 Jun 10 21:22 binlog.000006
-rw-r-----. 1 mysql mysql  154 Jun 10 21:22 binlog.000007
-rw-r-----. 1 mysql mysql  273 Jun 10 21:22 binlog.index

#拷贝binlog文件
[root@localhost binlog]# cp binlog.000006 /tmp/

3.6 准备第二份增量数据

mysql> use test_backup; 
mysql> insert into increment values (16, 'increment16'),(17, 'increment17'),(18, 'increment18'),(19, 'increment19'),(20, 'increment20');

3.7 将第二份增量数据进行备份

#将日志刷到当前的binlog文件中,也就是binlog.000007,数据库再有新的数据更新会记录在新的binlog(binlog.000008)里面.
mysql> flush logs;  

[root@localhost binlog]# ll
total 36
-rw-r-----. 1 mysql mysql  177 Jun  6 00:03 binlog.000001
-rw-r-----. 1 mysql mysql  177 Jun  6 00:03 binlog.000002
-rw-r-----. 1 mysql mysql  981 Jun  6 00:07 binlog.000003
-rw-r-----. 1 mysql mysql  177 Jun  6 00:07 binlog.000004
-rw-r-----. 1 mysql mysql  177 Jun  7 01:39 binlog.000005
-rw-r-----. 1 mysql mysql 1658 Jun 10 21:22 binlog.000006
-rw-r-----. 1 mysql mysql  603 Jun 10 22:04 binlog.000007
-rw-r-----. 1 mysql mysql  154 Jun 10 22:04 binlog.000008
-rw-r-----. 1 mysql mysql  312 Jun 10 22:04 binlog.index

#拷贝binlog文件
[root@localhost binlog]# cp binlog.000007 /tmp/

四. mysql还原实例分析(全备还原+基于binlog的增备还原)

#模拟数据库故障,即删除全备数据及增备数据库。
mysql> drop database test_backup;
Query OK, 2 rows affected (0.06 sec)

mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| sys                |
+--------------------+

4.1 还原全备数据

mysql>create database test_backup;

[root@localhost binlog]# mysql -uroot   -proot  test_backup   use test_backup;
Database changed
mysql> show tables;
+-----------------------+
| Tables_in_test_backup |
+-----------------------+
| t_test                |
+-----------------------+

mysql> select * from t_test;
+------+--------+
| c1   | c2     |
+------+--------+
|    1 | test1  |
|    2 | test2  |
|    3 | test3  |
|    4 | test4  |
|    5 | test5  |
|    6 | test6  |
|    7 | test7  |
|    8 | test8  |
|    9 | test9  |
|   10 | test10 |
+------+--------+

4.2 还原第一个增备文件

从全备文件里的position值:LOG_FILE=’binlog.000006′, MASTER_LOG_POS=996 开始还原

[root@localhost tmp]# mysqlbinlog --start-position=996   binlog.000006 | mysql -uroot -proot

#查看数据:
mysql> select * from test_backup.increment; 
+------+------------+
| c1   | c2         |
+------+------------+
|   11 | increment1 |
|   12 | increment2 |
|   13 | increment3 |
|   14 | increment4 |
|   15 | increment5 |
+------+------------+

第一份增量数据还原成功!

4.3 还原第二个增备文件(方法同上)

[root]mysqlbinlog  mysqlbinlog    binlog.000007 | mysql -uroot -proot

查看数据:

mysql> select * from test_backup.increment;
+------+-------------+
| c1   | c2          |
+------+-------------+
|   11 | increment1  |
|   12 | increment2  |
|   13 | increment3  |
|   14 | increment4  |
|   15 | increment5  |
|   16 | increment16 |
|   17 | increment17 |
|   18 | increment18 |
|   19 | increment19 |
|   20 | increment20 |
+------+-------------+ 

全部数据还原成功!

转自:https://www.cnblogs.com/chinesern/p/9182962.html

mysql备份与还原

一、备份常用操作基本命令

1、备份命令mysqldump格式

   格式:mysqldump -h主机名  -P端口 -u用户名 -p密码 –database 数据库名 > 文件名.sql 

2、备份MySQL数据库为带删除表的格式

备份MySQL数据库为带删除表的格式,能够让该备份覆盖已有数据库而不需要手动删除原有数据库。

mysqldump  –add-drop-table -uusername -ppassword -database databasename > backupfile.sql

3、直接将MySQL数据库压缩备份

mysqldump -hhostname -uusername -ppassword -database databasename | gzip > backupfile.sql.gz

4、备份MySQL数据库某个(些)表

mysqldump -hhostname -uusername -ppassword databasename specific_table1 specific_table2 > backupfile.sql

5、同时备份多个MySQL数据库

mysqldump -hhostname -uusername -ppassword –databases databasename1 databasename2 databasename3 > multibackupfile.sql仅仅备6、仅备份份数据库结构

mysqldump –no-data –databases databasename1 databasename2 databasename3 > structurebackupfile.sql

7、备份服务器上所有数据库

mysqldump –all-databases > allbackupfile.sql

8、还原MySQL数据库的命令

mysql -hhostname -uusername -ppassword databasename < backupfile.sql

9、还原压缩的MySQL数据库

gunzip < backupfile.sql.gz | mysql -uusername -ppassword databasename

10、将数据库转移到新服务器

mysqldump -uusername -ppassword databasename | mysql –host=*.*.*.* -C databasename

11、–master-data 和–single-transaction

   在mysqldump中使用–master-data=2,会记录binlog文件和position的信息 。–single-transaction会将隔离级别设置成repeatable-commited

12、导入数据库

常用source命令,用use进入到某个数据库,mysql>source d:\test.sql,后面的参数为脚本文件。

13、查看binlog日志

查看binlog日志可用用命令 mysqlbinlog  binlog日志名称|more

 

14、general_log

General_log记录数据库的任何操作,查看general_log 的状态和位置可以用命令show variables like “general_log%”  ,开启general_log可以用命令set global general_log=on

二、增量备份

小量的数据库可以每天进行完整备份,因为这也用不了多少时间,但当数据库很大时,就不太可能每天进行一次完整备份了,这时候就可以使用增量备份。增量备份的原理就是使用了mysql的binlog志。

 

1、首先做一次完整备份:

mysqldump -h10.6.208.183 -utest2 -p123  -P3310 –single-transaction  –master-data=2  test>test.sql这时候就会得到一个全备文件test.sql

在sql文件中我们会看到:
— CHANGE MASTER TO MASTER_LOG_FILE=’bin-log.000002′, MASTER_LOG_POS=107;是指备份后所有的更改将会保存到bin-log.000002二进制文件中。
2、在test库的t_student表中增加两条记录,然后执行flush logs命令。这时将会产生一个新的二进制日志文件bin-log.000003,bin-log.000002则保存了全备过后的所有更改,既增加记录的操作也保存在了bin-log.00002中。

3、再在test库中的a表中增加两条记录,然后误删除t_student表和a表。a中增加记录的操作和删除表a和t_student的操作都记录在bin-log.000003中。

 

三、恢复

1、首先导入全备数据

mysql -h10.6.208.183 -utest2 -p123  -P3310 < test.sql,也可以直接在mysql命令行下面用source导入

2、恢复bin-log.000002

   mysqlbinlog bin-log.000002 |mysql -h10.6.208.183 -utest2 -p123  -P3310  

3、恢复部分 bin-log.000003

   在general_log中找到误删除的时间点,然后更加对应的时间点到bin-log.000003中找到相应的position点,需要恢复到误删除的前面一个position点。

可以用如下参数来控制binlog的区间

–start-position 开始点 –stop-position 结束点

–start-date 开始时间  –stop-date  结束时间

找到恢复点后,既可以开始恢复。

  mysqlbinlog mysql-bin.000003 –stop-position=208 |mysql -h10.6.208.183 -utest2 -p123  -P3310

转自:https://www.cnblogs.com/adolfmc/p/9822389.html

使用xkbeancomparator对比javabean,生成操作记录

xkbeancomparator 是一个 java bean 对比修改并输出差异的工具。github地址

适用场景:用户编辑提交时,需要记录修改内容,修改前后的值对比,生成操作记录;可以选择记录的字段和字段说明,自定义操作记录。

特点:

jdk 1.7+

不依赖第三方jar,大小非常小

使用反射,调用get方法对比字段值。

 

使用实例:xkbeancomparator-samples

(1)添加pom依赖

  
  com.github.xkzhangsan    
  xkbeancomparator       
  0.0.1    
    

(2)java bean类 User

import java.math.BigDecimal;

public class User {
    Integer id;
    String name;
    private BigDecimal point;
    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public BigDecimal getPoint() {
        return point;
    }
    public void setPoint(BigDecimal point) {
        this.point = point;
    }
    

}

 

(3)增加辅助日志类 UserLog

import java.util.HashMap;
import java.util.Map;

import com.xkzhangsan.xkbeancomparator.BeanComparator;
import com.xkzhangsan.xkbeancomparator.CompareResult;

public class UserLog{

private static final Map propertyTranslationMap = new HashMap<>();

static {
	propertyTranslationMap.put("name", "用户名");
	propertyTranslationMap.put("point", "积分");
}

public static CompareResult getCompareResult(Object source, Object target){
	return BeanComparator.getCompareResult(source, target, propertyTranslationMap);
}

}

(4) 使用

@Test
public void test1() {
	User u1 = new User();
	u1.setId(1);
	u1.setName("aa");
	u1.setPoint(new BigDecimal("111111111111.12"));

	User u2 = new User();
	u2.setId(1);
	u2.setName("aa2");
	u2.setPoint(new BigDecimal("111111111111.15"));
	CompareResult compareResult = UserLog.getCompareResult(u1, u2);
	if (compareResult.isChanged()) {
		System.out.println(compareResult.getChangeContent());
	}
}

(5)输出结果

用户名:aa->aa2,积分:111111111111.12->111111111111.15,

(6)说明 instructions

上面是推荐用法,使用辅助日志类能统一维护一个java bean的注释map,简化调用。 The recommended usage, above, is to use secondary logging classes to uniformly maintain an annotated map of a Java bean, simplifying invocation.

欢迎提建议 Suggestions are welcome!

SSO单点登录和CAS

一、单点登录流程

=====客户端======

1.拦截客户端的请求判断是否有局部的session

2.1如果有局部的session,放行请求.

2.2如果没有局部session

2.2.1请求中有携带token参数

2.2.1.1如果有,使用HttpURLConnection发送请求校验token是否有效.

2.2.1.1.1如果token有效,建立局部的session.

2.2.1.1.2如果token无效,重定向到统一认证中心页面进行登陆.

2.2.1.2如果没有,重定向到统一认证中心页面进行登陆.

2.2.2请求中没有携带token参数,重定向到统一认证中心页面进行登陆.

=====服务端=====

1.检测客户端在服务端是否已经登录了.(checkLogin方法)
1.1获取session中的token.
1.2如果token不为空,说明服务端已经登录过了,此时重定向到客户端的地址,并把token带上
1.3如果token为空,跳转到统一认证中心的的登录页面,并把redirectUrl放入到request域中.

2.统一认证中心的登录方法(login方法)
2.1判断用户提交的账号密码是否正确.
2.2如果正确
2.2.1创建token(可以使用UUID,保证唯一就可以)
2.2.2把token放入到session中,还需要把token放入到数据库表t_token中
2.2.3这个token要知道有哪些客户端登陆了,存入数据库t_client_info表中.);
2.2.4转发到redirectUrl地址,把token带上.
2.3如果错误
转发到login.jsp,还需要把redirectUrl参数放入到request域中.

3.统一认证中心认证token方法(verifyToken方法),返回值为String,贴@ResponseBody
3.1如果MockDatabaseUtil.T_TOKEN.contains(token)结果为true,说明token是有效的.
3.1.1返回true字符串.
3.1如果MockDatabaseUtil.T_TOKEN.contains(token)结果为false,说明token是无效的,返回false字符串.

二、单点注销流程

=====客户端======

1.在登陆的按钮链接写上统一认证中心的登出方法即可.
退出
2.校验令牌信息的同时把客户端登出地址(clientUrl)和客户端会话id(jsession)一并的传到统一认证中心

=====服务端=====

1.编写logOut方法,调用session.invalidate()
2.创建session的监听器,在session的监听器的销毁方法写如下逻辑
2.1获取session中的token.
2.2根据token获取所有客户端的登出地址和会话id
2.3通过HttpUtil选项调用客户端的登出方法.

三、开源软件CAS

CAS(Central Authentication Service) 是 Yale (耶鲁)大学发起的开源的企业级单点登录系统。它的特点:

Java (Spring Webflow/Spring Boot) 服务组件
可插拔身份验证支持(LDAP,Database,X.509,MFA)
支持多种协议(CAS,SAML,OAuth,OpenID,OIDC)
跨平台客户端支持(Java,.Net,PHP,Perl,Apache等)
与uPortal,Liferay,BlueSocket,Moodle,Google Apps等集成
分为CAS Server服务端和CAS Client客户端:

CAS Server:
CAS Server 负责完成对用户的认证工作, CAS Server 需要独立部署,有不止一种 CAS Server 的实现, Yale CAS Server 和 ESUP CAS Server 都是很不错的选择。
CAS Server 会处理用户名 / 密码等凭证 (Credentials) ,它可能会到数据库检索一条用户帐号信息,也可能在 XML 文件中检索用户密码,对这种方式, CAS 均提供一种灵活但同一的接口 / 实现分离的方式, CAS 究竟是用何种认证方式,跟 CAS 协议是分离的,也就是,这个认证的实现细节可以自己定制和扩展.

CAS Client:
CAS Client 负责部署在客户端(注意,我是指 Web 应用),原则上, CAS Client 的部署意味着,当有对本地 Web 应用的受保护资源的访问请求,并且需要对请求方进行身份认证, Web 应用不再接受任何的用户名密码等类似的 Credentials ,而是重定向到 CAS Server进行认证。
目前, CAS Client 支持(某些在完善中)非常多的客户端,包括 Java 、 .Net 、 ISAPI 、 Php 、 Perl 、 uPortal 、 Acegi 、 Ruby 、VBScript 等客户端,几乎可以这样说, CAS 协议能够适合任何语言编写的客户端应用。

第一二部分的源码:https://github.com/yyzmain/ssoDemo

转自:https://blog.csdn.net/wolfcode_cn/article/details/80773274

 

SpringCloud的阿里巴巴相关开源组件

 

Sentinel

阿里巴巴开源产品,把流量作为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。

Nacos

阿里巴巴开源产品,一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。

RocketMQ

Apache RocketMQ™ 基于 Java 的高性能、高吞吐量的分布式消息和流计算平台。

Dubbo

Apache Dubbo™ 是一款高性能 Java RPC 框架。

Seata

阿里巴巴开源产品,一个易于使用的高性能微服务分布式事务解决方案。

 

参考:https://github.com/alibaba/spring-cloud-alibaba