自动填充

创建时间、修改时间!这些这个操作自编都是自动化实现的,我们不希望手动更新!

阿里巴巴开发手册:所有的数据库表:gmt_create\gmt_modilfed几乎所有的表都要配置上!而且自动化!

方法一:数据库级别

1.在表中新增字段create_time\update_time

dateTime类型

自动生成

方法二:代码级别

1.删除数据库的默认值

2.实体类上增加相应的注解

@TableField(fill = FieldFill.INSERT)
private Long createUser;

@TableField(fill = FieldFill.INSERT_UPDATE)
private Long updateUser;

3.处理注解,编写处理器

4.测试插入

5.测试更新

乐观锁

在面试过程中,经常被问到乐观锁------悲观锁(原子引用)

认为不会出现问题,无论干什么不去上锁,如果出现了问题,就再次更新值测试

version、new version

悲观锁,认为都会出现问题,无论干什么都会上锁

乐观锁的实现

  • 取出记录
  • 更新时,带上version
  • 执行更新操作时,set version = newVersion where version = oldVersion()
  • 如果version不对,就更新失败
乐观锁:1.先查询,获得版本号 version=1
--A
update user set name ="郝泾钊",version=version+1
where id =2 and version =1
--B 线程抢先完成 这时候 version=2,就会导致A修改失败
update user set name ="郝泾钊",version=version+1
where id =2 and version =1

Mybatis Plus 的乐观锁字段

1.增加version字段,默认值为1

2.实体类加上version字段

//添加version注解
@Version
private Integer version;

3.注册组件

要添加@Configuration注解

//注册乐观锁的插件
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
    MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
    interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
    return interceptor;
}
package com.ithema.reggie.config;

import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;

/**
 * 配置MP的分页插件
 */
@MapperScan("com.ithema.reggie.mapper")
@EnableTransactionManagement
@Configuration
public class MybatisPlusConfig {

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
        MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
        mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
        return mybatisPlusInterceptor;
    }


}

4.测试乐观锁

@Test
public void OptimisticLockerInnerInterceptor(){
 //1.查询用户信息
 User us = userMapper.selectById("1L");
 //修改用户质量
 us.setName("郝泾钊");
 us.setAge("20");
    
    //并发插队
     User us = userMapper.selectById("1L");
 //修改用户质量
 us.setName("郝泾钊");
 us.setAge("20");
     //执行更新操作
 userMapper.updateById(us);
 //执行更新操作
 //自旋锁自己就能行了
 userMapper.updateById(us);
}

分布式系统唯一id

雪花算法:

snowflake算法

snowflake是Twitter开源的分布式ID生成算法,结果是一个long型的ID。
其核心思想是:使用41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID),12bit作为毫秒内的流水号,最后还有一个符号位,永远是0。
这个算法单机每秒内理论上最多可以生成1000*(2^12),也就是409.6万个ID。

在这里插入图片描述
雪花算法描述:

最高位是符号位,始终为0,不可用。
41位的时间序列,精确到毫秒级,41位的长度可以使用69年。时间位还有一个很重要的作用是可以根据时间进行排序。
10位的机器标识,10位的长度最多支持部署1024个节点。10位器标识符一般是5位IDC+5位machine编号,唯一确定一台机器。
12位的计数序列号,序列号即一系列的自增id,可以支持同一节点同一毫秒生成多个ID序号,12位的计数序列号支持每个节点每毫秒产生4096个ID序号。

snowflake算法可以根据自身项目的需要进行一定的修改。比如估算未来的数据中心个数,每个数据中心的机器数以及统一毫秒可以能的并发数来调整在算法中所需要的bit数。

优点:
1)不依赖于数据库,速度快,性能高。
2)ID按照时间在单机上是递增的。
3)可以根据实际情况调整各各位段,方便灵活。

缺点:
1)在单机上是递增的,由于涉及到分布式环境,每台机器上的时钟不可能完全同步,有时也会出现不是全局递增的情况。
2)只能趋势递增。(如果绝对递增,竞对中午下单,第二天再下单即可大概判断该公司的订单量,危险!)
3)依赖机器时间,如果发生回拨会导致可能生成id重复。

算法的java实现:

public class SnowflakeIdWorker {
/** 开始时间截 (2015-01-01) */
private final long twepoch = 1420041600000L;

/** 机器id所占的位数 */
private final long workerIdBits = 5L;

/** 数据标识id所占的位数 */
private final long datacenterIdBits = 5L;

/** 支持的最大机器id,结果是31 (这个移位算法可以很快的计算出几位二进制数所能表示的最大十进制数) */
private final long maxWorkerId = -1L ^ (-1L << workerIdBits);

/** 支持的最大数据标识id,结果是31 */
private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);

/** 序列在id中占的位数 */
private final long sequenceBits = 12L;

/** 机器ID向左移12位 */
private final long workerIdShift = sequenceBits;

/** 数据标识id向左移17位(12+5) */
private final long datacenterIdShift = sequenceBits + workerIdBits;

/** 时间截向左移22位(5+5+12) */
private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;

/** 生成序列的掩码,这里为4095 (0b111111111111=0xfff=4095) */
private final long sequenceMask = -1L ^ (-1L << sequenceBits);

/** 工作机器ID(0~31) */
private long workerId;

/** 数据中心ID(0~31) */
private long datacenterId;

/** 毫秒内序列(0~4095) */
private long sequence = 0L;

/** 上次生成ID的时间截 */
private long lastTimestamp = -1L;

public SnowflakeIdWorker(long workerId, long datacenterId) {
    if (workerId > maxWorkerId || workerId < 0) {
        throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
    }
    if (datacenterId > maxDatacenterId || datacenterId < 0) {
        throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
    }
    this.workerId = workerId;
    this.datacenterId = datacenterId;
}

/**
 * 获得下一个ID (该方法是线程安全的)
 * @return SnowflakeId
 */
public synchronized long nextId() {
    long timestamp = timeGen();

    //如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常
    if (timestamp < lastTimestamp) {
        throw new RuntimeException(String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
    }

    //如果是同一时间生成的,则进行毫秒内序列
    if (lastTimestamp == timestamp) {
        sequence = (sequence + 1) & sequenceMask;
        //毫秒内序列溢出
        if (sequence == 0) timestamp = tilNextMillis(lastTimestamp);//阻塞到下一个毫秒,获得新的时间戳
    } else {//时间戳改变,毫秒内序列重置
        sequence = 0L;
    }

    //上次生成ID的时间截
    lastTimestamp = timestamp;
    //移位并通过或运算拼到一起组成64位的ID
    return ((timestamp - twepoch) << timestampLeftShift)
            | (datacenterId << datacenterIdShift)
            | (workerId << workerIdShift)
            | sequence;
}

/**
 * 阻塞到下一个毫秒,直到获得新的时间戳
 * @param lastTimestamp 上次生成ID的时间截
 * @return 当前时间戳
 */
protected long tilNextMillis(long lastTimestamp) {
    long timestamp = timeGen();
    while (timestamp <= lastTimestamp) {
        timestamp = timeGen();
    }
    return timestamp;
}

/**
 * 返回以毫秒为单位的当前时间
 * @return 当前时间(毫秒)
 */
protected long timeGen() {
    return System.currentTimeMillis();
}

public static void main(String[] args) {
    SnowflakeIdWorker idWorker = new SnowflakeIdWorker(0, 0);
    for (int i = 0; i < 1000; i++) {
        long id = idWorker.nextId();
        System.out.println(Long.toBinaryString(id));
        System.out.println(id);
    }
}

}

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

snowflake算法时间回拨问题
分析时间回拨产生原因:
1)人为操作,在真实环境一般不会出现,基本可以排除。
2)由于有些业务的需要,机器需要同步时间服务器(在这个过程中可能会存在时间回拨)。

时间问题回拨的解决方法:
1)当回拨时间小于15ms,就等时间追上来后继续生成。
2)当时间大于15ms,通过更换workid来产生之前都没有产生过的来解决。
3)把workid的位数进行调整(15位可以达到3万多,一般够用了)

在这里插入图片描述
Snowflake算法调整下位段:

sign(1bit):固定1bit符号标识,即生成的畅途分布式唯一id为正数。
delta seconds (38 bits):当前时间,相对于时间基点"2017-12-21"的增量值,单位:毫秒,最多可支持约8.716年。
worker id (15 bits):机器id,最多可支持约3.28万个节点。
sequence (10 bits):每秒下的并发序列,10 bits,这个算法单机每秒内理论上最多可以生成1000*(2^10),也就是100W的ID,完全能满足业务的需求。

由于服务无状态化关系,所以一般workid也并不配置在具体配置文件里面,这里我们选择redis来进行中央存储(zk、db)都是一样的,只要是集中式的就可以。
现在把3万多个workid放到一个队列中(基于redis),由于需要一个集中的地方来管理workId,每当节点启动时,(先在本地某个地方看看是否有借鉴弱依赖zk本地先保存),如果有那么值就作为workid,如果不存在,就在队列中取一个当workid来使用(队列取走了就没了 ),当发现时间回拨太多的时候,我们就再去队列取一个来当新的workid使用,把刚刚那个使用回拨的情况的workid存到队列里面(队列我们每次都是从头取,从尾部进行插入,这样避免刚刚a机器使用又被b机器获取的可能性)。

主键的生成策略

默认全局唯一id
@TableId(type = IdType.ID_WORKER)
主键自增
数据库也必须是自增的不然报错
@TableId(type = IdType.AUTO)
其他的源码解释:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.baomidou.mybatisplus.annotation;

public enum IdType {
    AUTO(0),//数据库id自增
    NONE(1),//未设置主键
    INPUT(2),//手动输入
    ASSIGN_ID(3),//默认的全局id
    ASSIGN_UUID(4),//默认的唯一id uuid
    /** @deprecated */
    @Deprecated
    ID_WORKER(3),
    /** @deprecated */
    @Deprecated
    ID_WORKER_STR(3),//字符串表示法
    /** @deprecated */
    @Deprecated
    UUID(4);

    private final int key;

    private IdType(int key) {
        this.key = key;
    }

    public int getKey() {
        return this.key;
    }
}
posted on 2022-06-24 23:02  Steam残酷  阅读(78)  评论(0)    收藏  举报