데이터베이스가 테이블로 분할된 후 기본 키 ID를 처리하는 방법은 무엇입니까?

머리말

        관계형 데이터베이스의 데이터 양이 너무 많은 경우 일반적으로 데이터베이스 테이블 조회 부담을 줄이기 위해 하위 데이터베이스와 테이블을 사용합니다. 하위 데이터베이스와 테이블에는 여러 유형이 있으며, 일부는 하나의 데이터베이스로 나누어져 여러 테이블을 갖고 있고, 일부는 여러 데이터베이스와 여러 테이블로 나누어져 있습니다. 일반적으로 ShardingSphere는 데이터베이스 및 테이블 샤딩, 샤딩 키 생성 등에 사용됩니다. 하지만 데이터베이스가 테이블로 분할된 후 기본 키 ID를 처리하는 방법은 무엇입니까? 동일한 비즈니스 테이블의 서로 다른 샤딩된 테이블의 기본키 ID는 동일할 수 없다.사실 데이터베이스와 테이블을 샤딩한 후 반드시 직면하게 되는 문제, 즉 기본키 ID를 어떻게 생성하는가? 여러 개의 테이블로 나누어져 있고, 각 테이블이 1부터 누적되기 시작한다면 그것은 확실히 잘못된 것이기 때문에, 이를 지원하려면 전역적으로 고유한 ID가 필요합니다. 따라서 실제 프로덕션 환경에서 고려해야 할 모든 문제는 다음과 같습니다.

다음은 내가 컴파일한 기본 키 ID를 처리하는 여러 가지 방법입니다.

1. 기본키 ID 자동 생성

이 방법은 일반적으로 기본 키를 자동 증가하는 bitint 유형으로 설정합니다. 하지만 문제가 있는데, 여러 하위 테이블은 기본 키가 충돌하지 않도록 보장하는데, 비즈니스 관점에서 여러 하위 테이블의 데이터가 특정 비즈니스를 구성하므로 기본 키 충돌이 허용되지 않기 때문입니다.
기본 키 ID 자동 생성 솔루션을 사용하면 고정된 여러 하위 테이블을 설정할 수 있습니다. 각 하위 테이블의 시작 지점은 다르며, 새로 추가되는 단계 크기는 다음과 같습니다. 동일하므로 각 하위 테이블의 기본 키가 충돌하지 않도록 보장됩니다.

데이터베이스 순서 또는 테이블의 자동 증가 필드 단계 크기를 설정하여 수평 스케일링을 수행할 수 있습니다. 현재 10개의 서비스 노드가 있으며, 각 서비스 노드는 시퀀스 함수를 사용하여 ID를 생성하며, 각 시퀀스의 시작 ID는 다르며 10단계 크기로 순차적으로 증가합니다.

예를 들어 특정 테이블에 10개의 하위 테이블이 있는 경우 각 테이블의 시작 기본 키 ID를 1부터 10까지 설정할 수 있으며, 각 하위 테이블의 기본 키 ID의 증가 단계는 10입니다.

테이블 이름 기본 키 ID 시작 단계 크기
1 번 테이블 1 10
table_2 2 10
table_3 10
table_4 4 10
table_5 5 10
table_6 6 10
table_7 7 10
table_8 8 10
table_9 9 10
table_10 10 10

위의 하위 테이블의 기본 키 증가 규칙에 따라 각 테이블의 행 수는 다음과 같이 증가합니다.

기본 키 증가 형식을 따르면 단점이 있습니다. 즉, 새 테이블을 추가할 때 기본 키 논리를 처리하기가 어렵습니다. 기본 키 ID를 증가시키는 이 방법은 하위 테이블이 상대적으로 고정되어 있는 상황에 적합합니다.

2. 기본 키로 UUID

장점은 데이터베이스를 기반으로 생성되지 않고 로컬로 생성된다는 점이며, 단점은 UUID가 너무 길고 공간을 많이 차지한다는 점입니다. 기본 키가 너무 나쁩니다< a i=2>. 더 중요한 것은 UUID가 순서가 지정되지 않아 B+ 트리 인덱스를 쓸 때 무작위 쓰기 작업이 너무 많이 발생한다는 것입니다(연속 ID는 부분 순차 쓰기를 생성할 수 있음). 쓰기 시 순차적 추가 작업은 생성할 수 없지만 삽입 작업이 필요합니다. 전체 B+ 트리 노드를 메모리로 읽습니다. 이 레코드를 삽입한 후 전체 노드를 디스크에 다시 씁니다. 이 작업은 레코드에서 더 많은 공간을 차지하게 되며, 큰 경우 성능이 크게 저하됩니다.

적합한 시나리오: 파일 이름, 번호 등을 무작위로 생성하려는 경우 UUID를 사용할 수 있지만 UUID를 기본 키로 사용할 수는 없습니다.

UUID.randomUUID().toString().replace("-", "") -> sfsdf23423rr234sfdaf

3. 현재 시스템 시간을 가져옵니다.

단순히 현재 시간을 가져오기 위한 것이지만 문제는동시성이 매우 높을 때 둘째, a>중복이 있을 것입니다. 이는 확실히 부적절합니다. 기본적으로 그것에 대해 생각할 필요가 없습니다.

적합한 시나리오: 일반적으로 이 솔루션을 사용하면 현재 시간을 다른 많은 비즈니스 분야와 ID로 결합하게 됩니다. 비즈니스 측면에서 허용된다고 생각한다면 그것도 허용됩니다. 다른 비즈니스 필드 값을 현재 시간과 연결하여 전역적으로 고유한 숫자를 형성할 수 있습니다.

4. 눈송이 알고리즘 눈송이

(1) 자리 표시자 1개: 기본값은 0입니다. 가장 높은 비트는 양수와 음수를 나타내고, 1은 음수를 나타내고, 0은 양수를 나타내며, 기본값은 양수입니다.
(2) 41비트 타임스탬프: 밀리초 수준의 시간, 69년 동안 저장 가능, (1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69년
(3) 5자리 작업 센터 ID: 소수점 범위는 0-31, 5자리 데이터 센터 ID: 소수점 범위는 0-31입니다. 두 개를 결합하면 최대 1024개의 노드를 수용할 수 있습니다.
(4) 일련번호 : 12비트를 차지하며 최대 4095까지 누적될 수 있다. 자체 증가 값은 동일한 노드가 동일한 밀리초에 4096개의 ID를 생성할 수 있도록 지원하며, 이 값은 동일한 밀리초에 동일한 노드에서 0부터 계속 누적됩니다. (단일 노드에서 거의 400만 개의 최대 동시성을 지원할 수 있습니다.)


눈송이 알고리즘은 트위터의 오픈 소스 분산 ID 생성 알고리즘으로 Scala 언어로 구현되었으며 64비트의 긴 ID를 사용하고 1비트는 사용되지 않으며 41비트는 밀리초로 사용하고 10비트는 작업 머신으로 사용합니다. 일련 번호는 12비트입니다.

•1비트: 아니요, 왜요? 이진수의 첫 번째 비트가 1이므로 모두 음수이지만 우리가 생성하는 ID는 모두 양수이므로 첫 번째 비트는 균일하게 0입니다.

•41비트: 타임스탬프를 나타내며 단위는 밀리초입니다. 41비트는 최대 2^41 - 1 자리, 즉 2^41 - 1 밀리초 값을 식별할 수 있으며, 성인으로 환산하면 69년을 나타냅니다.

·10비트: 작업 머신 ID를 기록합니다. 이는 이 서비스가 최대 2^10 머신, 즉 1024 머신에 배포될 수 있음을 의미합니다. 그러나 10비트 중 5비트는 컴퓨터실 ID를 나타내고 5비트는 컴퓨터 ID를 나타냅니다. 즉, 최대 2^5 컴퓨터실(32개 컴퓨터실)을 나타낼 수 있으며 각 컴퓨터실은 2^5 컴퓨터(32개 컴퓨터실)를 나타낼 수 있습니다.

•12비트: 동일한 밀리초 내에 생성된 서로 다른 ID를 기록하는 데 사용됩니다. 12비트가 나타낼 수 있는 가장 큰 양의 정수는 2^12 - 1 = 4096 입니다. 즉, 이 12비트는 다음과 같습니다. 사용된 숫자는 동일한 밀리초 동안 4096개의 서로 다른 ID를구별합니다.

0 | 0001100 10100010 10111110 10001001 01011100 00 | 10001 | 1 1001 | 0000 00000000
public class IdWorker {

    private long workerId;
    private long datacenterId;
    private long sequence;

    public IdWorker(long workerId, long datacenterId, long sequence) {
        // sanity check for workerId
        // 这儿不就检查了一下,要求就是你传递进来的机房id和机器id不能超过32,不能小于0
        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));
        }
        System.out.printf(
                "worker starting. timestamp left shift %d, datacenter id bits %d, worker id bits %d, sequence bits %d, workerid %d",
                timestampLeftShift, datacenterIdBits, workerIdBits, sequenceBits, workerId);

        this.workerId = workerId;
        this.datacenterId = datacenterId;
        this.sequence = sequence;
    }

    private long twepoch = 1288834974657L;

    private long workerIdBits = 5L;
    private long datacenterIdBits = 5L;

    // 这个是二进制运算,就是 5 bit最多只能有31个数字,也就是说机器id最多只能是32以内
    private long maxWorkerId = -1L ^ (-1L << workerIdBits);

    // 这个是一个意思,就是 5 bit最多只能有31个数字,机房id最多只能是32以内
    private long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
    private long sequenceBits = 12L;

    private long workerIdShift = sequenceBits;
    private long datacenterIdShift = sequenceBits + workerIdBits;
    private long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
    private long sequenceMask = -1L ^ (-1L << sequenceBits);

    private long lastTimestamp = -1L;

    public long getWorkerId() {
        return workerId;
    }

    public long getDatacenterId() {
        return datacenterId;
    }

    public long getTimestamp() {
        return System.currentTimeMillis();
    }

    public synchronized long nextId() {
        // 这儿就是获取当前时间戳,单位是毫秒
        long timestamp = timeGen();

        if (timestamp < lastTimestamp) {
            System.err.printf("clock is moving backwards.  Rejecting requests until %d.", lastTimestamp);
            throw new RuntimeException(String.format(
                    "Clock moved backwards.  Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
        }

        if (lastTimestamp == timestamp) {
            // 这个意思是说一个毫秒内最多只能有4096个数字
            // 无论你传递多少进来,这个位运算保证始终就是在4096这个范围内,避免你自己传递个sequence超过了4096这个范围
            sequence = (sequence + 1) & sequenceMask;
            if (sequence == 0) {
                timestamp = tilNextMillis(lastTimestamp);
            }
        } else {
            sequence = 0;
        }

        // 这儿记录一下最近一次生成id的时间戳,单位是毫秒
        lastTimestamp = timestamp;

        // 这儿就是将时间戳左移,放到 41 bit那儿;
        // 将机房 id左移放到 5 bit那儿;
        // 将机器id左移放到5 bit那儿;将序号放最后12 bit;
        // 最后拼接起来成一个 64 bit的二进制数字,转换成 10 进制就是个 long 型
        return ((timestamp - twepoch) << timestampLeftShift) | (datacenterId << datacenterIdShift)
                | (workerId << workerIdShift) | sequence;
    }

    private long tilNextMillis(long lastTimestamp) {
        long timestamp = timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = timeGen();
        }
        return timestamp;
    }

    private long timeGen() {
        return System.currentTimeMillis();
    }

    // ---------------测试---------------
    public static void main(String[] args) {
        IdWorker worker = new IdWorker(1, 1, 1);
        for (int i = 0; i < 30; i++) {
            System.out.println(worker.nextId());
        }
    }

}

어떻게 표현하나요? 아마도 41비트는 현재 밀리초 단위의 타임스탬프이고, 그런 의미입니다. 그리고 5비트는 전달한 비트입니다.컴퓨터실  ID(단, 최대값은 32 이내여야 함), 나머지 5비트는 머신에 전달한 것입니다.  id(단, 최대 개수는 32개 이내여야 함), 나머지 12비트 일련 번호는 마지막으로 ID를 생성한 시간으로부터 1밀리초 이내인 경우 시퀀스가 ​​누적된다는 의미입니다. , 최대 4096개의 일련 번호.

그래서 이 툴클래스를 이용하여 직접 서비스를 만든 후, 각 전산실의 각 기계에 대해 그런 것을 초기화하는데, 처음에는 이 전산실에 있는 기계의 일련번호가 0 입니다. 그러면 이 컴퓨터실에 있는 이 기계가 ID를 생성해야 한다는 요청을 받을 때마다 생성할 해당 Worker를 찾을 수 있습니다.

이 눈송이 알고리즘을 사용하면 자신만의 회사 서비스를 개발할 수 있습니다. 전산실 ID와 머신 ID도 어쨌든 5비트 + 5비트가 예약되어 있으며 비즈니스 의미가 있는 다른 것으로 변경할 수도 있습니다.

이 눈송이 알고리즘은 상대적으로 안정적이므로 실제로 분산 ID 생성에 참여하고 동시성이 높으면 이것을 사용하는 것이 더 나은 성능을 가져야 합니다. 일반적으로 초당 수만 개의 동시성 시나리오를 사용하면 충분합니다. ...

요약하다:

위의 ID 생성 알고리즘 외에도 아직 정리되지 않은 기본 키 ID 생성 알고리즘도 물론 존재하며, 구체적인 사용 방법은 비즈니스 상황에 따라 사용해야 합니다.

추천

출처blog.csdn.net/m0_61243965/article/details/134409932