数据库分库分表-需要分布式全局Id

57 篇文章 0 订阅
25 篇文章 1 订阅

在项目开发中,随着业务的不断发展数据日增增大。这个时候就会出现数据表的分库分表的操作。一旦进行了分库和分表操作。传统的id就失去了意义。所以需要分布式全局ID。

分布式全局ID的特点

1:全局唯一性,不能出现重复的ID
2:单调递增,保证下一个ID一定大于上一个ID
3:范围趋势递增。在一个时间段内,生成的ID是递增趋势的比如:202012120001 202012120002….
第二天的时候又要从1开始计数。202012130001 202012130002……
4:安全性,在不同的领域中我们有些业务不要出现连续的递增,可以很好的保护数据格式和形态。因为很容易让竞争对手套取数据。

上述的2和4是互斥的。无法同时满足。

实现方式

  • UUID
  • Redis
  • Twitter的雪花算法
  • 美团的Leaf算法

redis实现分布式全局唯一ID

第一种:单调递增的的具体实现:

就我个人看来,redisAtomicLong做分布式ID还是有一定的局限性。单纯针对redisAtomicLong数据丢失的解决方案,如果有什么好的建议,期待您的继续分享
思路:调用incr命令
1:Redis的incr命令具备了incr and get的原子性操作,即增加了结果的原子性操作,这个原子性操作很方便实现了唯一的全局分布式ID。
2:Redis本身是单线程架构。INCR命令不会出现重复的ID

代码:

1:采用redis的incr命令,从1自增id.
2:海量的数据存储一般都采用分库分表来进行存储。一般最多分1024个表,一般建表如:product_0,product_1,product_2……product_1023个表。

service

package com.kuangstudy.service.creatorid;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.support.atomic.RedisAtomicLong;
import org.springframework.stereotype.Service;

@Service
@Log4j2
public class RedisCreatorIdService {
    @Autowired
    private RedisTemplate redisTemplate;
    /**
     * @Description 生成全局id
     * @Date 18:50 2021/5/20
     **/
    public Long incrmentid() {
        RedisAtomicLong entityIdCounter = new RedisAtomicLong("id:creator:product", redisTemplate.getConnectionFactory());//0
        // 计数器累加 
        Long increment = entityIdCounter.incrementAndGet();
		if (null == incr || incr.longValue() == 0) {// 初始设置过期时间
				// 设置过期时间 24小时
				entityIdCounter.expire(24, TimeUnit.HOURS);
				// 这里取第二次 incr 就是从1开始了,默认从0开始
				incr = entityIdCounter.getAndIncrement();
			}
			log.debug(keyName + "==========" + incr);
			return incr;

        // 或者
        //Long increment = redisTemplate.opsForValue().increment(key);
        //return increment;
    }
}

controller

package com.kuangstudy.controller.creatorid;
import com.kuangstudy.entity.Product;
import com.kuangstudy.service.creatorid.RedisCreatorIdService;
import com.kuangstudy.vo.R;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ProductController {
    @Autowired
    private RedisCreatorIdService redisCreatorIdService;
    @PostMapping("/product/creator")
    public R creatorIds() {
        // 1:创建产品的分布式全局ID
        Long productid = redisCreatorIdService.incrmentid();
        // 2:创建产品
        Product product = new Product();
        product.setId(productid);
        product.setTitle("title:" + productid);
        System.out.println("保存产品是:" + product.toString());
        // 3: 存储到指定的数据库中
        int table = (int) (productid % 1024);
        System.out.println("保存到的数据库表是:product_" + table);
        return R.ok().data("tablename", "product_" + table);
    }
}

基于范围的规则递增的分布式全局id

比如:系统需要生成根据业务类型生成流水号,每天从1开始生成,第二天会清零继续从0开始,流水号格式为: bizCode + date + incr 如:TT-201711230000100。

思路:
1、利用Redis Incr 生成序列号,使用日期加业务编码作为组合Key,这样保证第二天生成的序列号又是从1开始。
2、由于我们业务量不是很大,这里在生成序列号之前先判断一下当前key是否存在,若不存在,设置此key过期时间为当天晚上23:59:59,避免生成很多过期的key。

service

package com.itheima.itheimadistributelock.redis;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.support.atomic.RedisAtomicLong;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
@Component
public class CacheService {
    //这里因为有其他的template,所以名字起得不好看
    @Autowired
    RedisTemplate redisTemplate;
    public Long getIncrementNum(String key) {
        // 不存在准备创建 键值对
        RedisAtomicLong entityIdCounter = new RedisAtomicLong(key, redisTemplate.getConnectionFactory());//0
        // 计数器累加 
        Long counter = entityIdCounter.incrementAndGet();
        System.out.println("=========================>"+ counter);
        if ((null == counter || counter.longValue() == 1)) {// 初始设置过期时间
            System.out.println("设置过期时间为1天!");
            // 设置清除的目的,是让每天的计数器都从0开始
            entityIdCounter.expire(1, TimeUnit.DAYS);// 单位天
        }
        return counter;
    }
}

utils

package com.kuangstudy.service.creatorid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
 * @Author 徐柯
 * @Description 统一订单流水号
 * @Date 19:29 2021/5/20
 * @Param
 * @return
 **/
@Component
public class SequenceUtils {
    @Autowired
    CacheService cacheService;
    // 1:每天公司的订单数量,如果一天是几百写 3 几千就 4 几万 5
    static final int DEFAULT_LENGTH = 3;
    public String getSequence() {
        // 1: 时间前缀
        String currentDate = new SimpleDateFormat("yyyyMMdd").format(new Date());
        // 2:redis递增获取每天的递增数量
        Long num = cacheService.getIncrementNum("id:generator:order:" + currentDate);
        // 3:获取递增长度,是否小于DEFAULT_LENGTH 如果小于就前面补零。如果大于就递增即可
        String str = String.valueOf(num);
        // 4:补零操作
        int len = str.length();
        // 4-1:是否小于DEFAULT_LENGTH 如果小于就前面补零。如果大于就递增即可
        StringBuilder sb = new StringBuilder();
        // 5:添加时间
        sb.append(currentDate);
        if (len >= DEFAULT_LENGTH) {
            sb.append(str);
            return sb.toString();
        }
        int rest = DEFAULT_LENGTH - len;
        for (int i = 0; i < rest; i++) {
            sb.append('0');
        }
        sb.append(str);
        // 6: 时间+补零操作返回订单流水号
        return sb.toString();
    }
}

controller

package com.kuangstudy.controller.creatorid;
/**
 * @author 飞哥
 * @Title: 学相伴出品
 * @Description: 我们有一个学习网站:https://www.kuangstudy.com
 * @date 2021/5/20 18:46
 */
@RestController
public class ProductController {
    @Autowired
    private SequenceUtils sequenceUtils;
    @PostMapping("/product/creator2")
    public R creatorIds2() {
        // 1:创建产品的分布式全局ID
        List<String> ids = new ArrayList<>();
        for (int i = 0; i < 100; i++) {
            String sequence = sequenceUtils.getSequence();
            ids.add(sequence);
        }
        return R.ok().data("ids", ids);
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

LC超人在良家

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值