Реализация распределенной глобальной службы уникальной идентификации с использованием Redis в Java

Java Redis задняя часть база данных

Введение в метод получения глобального уникального идентификатора

В ИТ-системе получение уникального идентификатора объекта является общим требованием. В предыдущем монолитном применении, если база данных является единой структурой базы данных. Этот уникальный идентификатор обычно может быть получен с помощью поля саморегулирования базы данных. Например, в базе данных MySQL мы можем создать таблицу с саморазомным типом поля INT через оператор SQL. Следующее.

CREATE TABLE student
(
    id INT NOT NULL AUTO_INCREMENT,
    name VARCHAR(16),
    PRIMARY KEY (id)
)

Затем вставьте два данных

INSERT INTO student(name) VALUE('yanggch');
INSERT INTO student(name) VALUE('grace');

Просмотр данных таблицы с помощью операторов SQL

 SELECT * FROM student;

получить следующий результат

数据库查询结果

Видно, что хоть мы и не указывали значение поля id при вставке данных через SQL, к двум записям автоматически добавляются два значения 1 и 2 из-за свойства самовозрастания AUTO_INCREMENT этого поля.

При таком подходе есть две основные проблемы. Во-первых, если это структура базы данных с подбазой данных и подтаблицей, то идентификаторы в одной и той же таблице, распределенные в разных экземплярах, дублируются. Другая проблема заключается в том, что после вставки записи в базу данных мы не можем знать в коде, каково значение первичного ключа записи, только что вставленной в базу данных. Если одна из наших задач состоит в том, чтобы одновременно вставить запись основной таблицы, серию записей подтаблицы с первичным ключом этой записи основной таблицы в качестве внешнего ключа, когда мы вставляем запись подтаблицы, мы не знать значение соответствующего внешнего ключа Сколько. Не могу вставить. Например, если у нас заказной бизнес, требуется вставить запись заказа в таблицу заказов, а в детали заказа одновременно вставить несколько записей подробной информации о товарах, купленных в этом заказе. После успешной вставки данных заказа нам неизвестно значение первичного ключа заказа, поэтому мы не можем правильно вставить запись сведений об элементе.

Другой способ использования атрибута поля автоинкремента базы данных для получения уникального идентификатора — создать таблицу данных с полем автоинкремента в базе данных. Каждый раз, когда запись вставляется в таблицу, значение этой записи извлекается как значение первичного ключа. Проблема в том, что вам нужно каждый раз вставлять в таблицу данных новую запись, в то же время в многопользовательской среде вы должны строго следить за тем, чтобы полученная вами запись была именно той записью, которую вы вставляете. В противном случае первичный ключ будет дублироваться. Это замедлит получение уникального идентификатора. В то же время этот метод не может сделать уникальный идентификатор глобально уникальным в структуре подбазы данных и подтаблицы.

Есть и другие способы. Например, использование алгоритма uuid может обеспечить глобальную уникальность и высокую производительность. Но он генерирует строку, которая не может гарантировать порядок, и она слишком длинная.

Итак, в распределенной архитектуре нам нужна служба уникальных идентификаторов, удовлетворяющая следующим условиям.

  1. глобально уникальный
  2. высокая производительность
  3. Последовательный
  4. Дополнительные бизнес-атрибуты могут быть прикреплены

Здесь мы можем использовать команду REDIS INCR в качестве глобального уникального идентификатора. Синтаксис команды INCR:

INCR key

Согласно официальному сайту RedisВведение в команду INCR, которая является атомарной операцией, результатом является увеличение значения ключа в базе данных Redis на единицу и возврат результата. Если ключ не существует, то перед выполнением операции сложения значение ключа будет установлено в 0, то есть результат выполнения этой команды будет суммироваться с 1.

В то же время мы видим, что временная сложность алгоритма этой команды составляет O(1), а данные redis хранятся в памяти, а скорость выполнения этой команды очень высока. В среде, где сервер redis является двухъядерным 16g, выполните стресс-тест в командной строке на другом сервере через гигабитную локальную сеть.

redis-benchmark -h 10.110.2.56 -p 52981 -a hhSbcpotThgWdnxJNhrzwstSP20DvYOldkjf

Результат выглядит следующим образом

redis INCR 压力测试结果
Видно, что в секунду может быть сгенерировано 50 000 логотипов. Это может удовлетворить общие требования к высокой производительности


Внедрить глобально уникальную службу идентификации с помощью Java и Redis.

Далее давайте продолжим использовать Redis в Java для реализации глобально уникальной службы. Эта услуга должна соответствовать следующим требованиям

  1. глобально уникальный
  2. высокая производительность
  3. Последовательный
  4. Номер даты может использоваться в качестве префикса глобального уникального идентификатора.
  5. Можно пересчитывать каждый день с 1
  6. Различные типы сущностей могут генерировать идентификаторы по отдельности. например, идентификатор заказа, идентификатор членства
  7. Может начать считать с 1 в новый день

Определение уникального интерфейса службы идентификации

package com.x9710.common.redis;

/**
 * 全局唯一标识服务接口
 *
 * @author 杨高超
 * @since 2017-12-10
 */
public interface UUIDService {

/**
 * 每天从 1 开始生成唯一标识
 *
 * @param key     要生成唯一标识的对象
 * @param length  要生成为唯一标识后缀的长度。不包括需要附加的时间前缀
 *                如果 haveDay = false 或者 length 长度小于标识后缀的长度则无效
 * @param haveDay 是否要附加日期前缀
 * @return 唯一标识
 * @throws Exception 异常
 */
Long fetchDailyUUID(String key, Integer length, Boolean haveDay) throws Exception;

/**
 * 全局从 1 开始生成唯一标识
 *
 * @param key     要生成唯一标识的对象
 * @param length  要生成为唯一标识后缀的长度。不包括需要附加的时间前缀
 *                如果 haveDay = false 或者 length 长度小于标识后缀的长度则无效
 * @param haveDay 是否要附加日期前缀。
 * @return 唯一标识
 * @throws Exception 异常
 */
Long fetchUUID(String key, Integer length, Boolean haveDay) throws Exception;
}

Внедрить сервис уникальной идентификации на основе Redis

package com.x9710.common.redis.impl;

import com.x9710.common.redis.RedisConnection;
import com.x9710.common.redis.UUIDService;
import redis.clients.jedis.Jedis;

import java.text.DateFormat;
import java.text.NumberFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.GregorianCalendar;

/**
 * @author 杨高超
 * @since 2017-11-19
 */
public class UUIDServiceRedisImpl implements UUIDService {
private RedisConnection redisConnection;
private Integer dbIndex;

private DateFormat df = new SimpleDateFormat("yyyyMMdd");

public void setRedisConnection(RedisConnection redisConnection) {
    this.redisConnection = redisConnection;
}

public void setDbIndex(Integer dbIndex) {
    this.dbIndex = dbIndex;
}

public Long fetchDailyUUID(String key, Integer length, Boolean haveDay) {
    Jedis jedis = null;
    try {
        jedis = redisConnection.getJedis();
        jedis.select(dbIndex);
        Calendar now = new GregorianCalendar();
        String day = df.format(now.getTime());
        //新的一天,通过新 key 获取值,每天都能从1开始获取
        key = key + "_" + day;
        Long num = jedis.incr(key);
        //设置 key 过期时间
        if (num == 1) {
            jedis.expire(key, (24 - now.get(Calendar.HOUR_OF_DAY)) * 3600 + 1800);
        }
        if (haveDay) {
            return createUUID(num, day, length);
        } else {
            return num;
        }
    } finally {
        if (jedis != null) {
            jedis.close();
        }
    }
}

public Long fetchUUID(String key, Integer length, Boolean haveDay) {
    Jedis jedis = null;
    try {
        jedis = redisConnection.getJedis();
        jedis.select(dbIndex);
        Calendar now = new GregorianCalendar();
        Long num = jedis.incr(key);
        
        if (haveDay) {
            String day = df.format(now.getTime());
            return createUUID(num, day, length);
        } else {
            return num;
        }
    } finally {
        if (jedis != null) {
            jedis.close();
        }
    }
}

private Long createUUID(Long num, String day, Integer length) {
    String id = String.valueOf(num);
    if (id.length() < length) {
        NumberFormat nf = NumberFormat.getInstance();
        nf.setGroupingUsed(false);
        nf.setMaximumIntegerDigits(length);
        nf.setMinimumIntegerDigits(length);
        id = nf.format(num);
    }
    return Long.parseLong(day + id);
}
}

Пишите тестовые случаи

Многопоточное тестирование не поддерживается в Junit4, поэтому метод main напрямую используется для запуска тестового примера.

package com.x9710.common.redis.test;

import com.x9710.common.redis.RedisConnection;
import com.x9710.common.redis.impl.UUIDServiceRedisImpl;

import java.util.Date;

public class RedisUUIDTest {

public static void main(String[] args) {
    for (int i = 0; i < 20; i++) {
        new Thread(new Runnable() {
            public void run() {
                RedisConnection redisConnection = RedisConnectionUtil.create();
                UUIDServiceRedisImpl uuidServiceRedis = new UUIDServiceRedisImpl();
                uuidServiceRedis.setRedisConnection(redisConnection);
                uuidServiceRedis.setDbIndex(15);
                try {
                    for (int i = 0; i < 100; i++) {
                        System.out.println(new Date() + " get uuid = " + 
                              uuidServiceRedis.fetchUUID("MEMBER", 8, Boolean.TRUE) + 
                              " by globle in " + Thread.currentThread().getName());
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }).start();

        new Thread(new Runnable() {
            public void run() {
                RedisConnection redisConnection = RedisConnectionUtil.create();
                UUIDServiceRedisImpl uuidServiceRedis = new UUIDServiceRedisImpl();
                uuidServiceRedis.setRedisConnection(redisConnection);
                uuidServiceRedis.setDbIndex(15);
                try {
                    for (int i = 0; i < 100; i++) {
                        System.out.println(new Date() + " get uuid = " + 
                            uuidServiceRedis.fetchDailyUUID("ORDER", 8, Boolean.TRUE) + 
                            " by daily in " + Thread.currentThread().getName());
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}
}

Результат выполнения следующий

Mon Dec 11 16:14:10 CST 2017 get uuid = 2017121100000003 by member in Thread-32
Mon Dec 11 16:14:10 CST 2017 get uuid = 2017121100000001 by member in Thread-8
Mon Dec 11 16:14:10 CST 2017 get uuid = 2017121100000007 by order in Thread-19
......
Mon Dec 11 16:14:14 CST 2017 get uuid = 2017121100002000 by member in Thread-14
Mon Dec 11 16:14:14 CST 2017 get uuid = 2017121100001999 by member in Thread-16
Mon Dec 11 16:14:14 CST 2017 get uuid = 2017121100001999 by order in Thread-39
Mon Dec 11 16:14:14 CST 2017 get uuid = 2017121100002000 by order in Thread-39

Таким образом, мы реализовали базовую услугу уникальной идентификации, которая удовлетворяет первым семи требованиям. Пока конфигурация сервера redis, подключенного программой, вызывающей этот модуль, одинакова, может быть реализована базовая служба, эффективно генерирующая уникальный идентификатор для того же объекта. Вы также можете обернуть это в службу отдыха.Клиенту не нужно напрямую подключаться к серверу Redis, но он может получить уникальный идентификатор удаленно через службу http остальных.

Эта программа в предыдущей статье«Зачем использовать кеш-сервер и реализовывать службу кеша Redis на Java»Это делается на основе добавления новых классов реализации. Код выпускается синхронно вРепозиторий GitHubсередина

Оригинальный текст был опубликован в брифе,оригинальная ссылка

Категории