zookeeper+redies 系统序列号生成

系统序列号生成服务是写的一个jar包,不依赖其他服务和数据,以下提供部分代码作为一个思路,作为大量数据订单生成时,不再使用数据库表的自增设置,由个系统模块自行生成。

一、使用规则

组成结构

+ 生成时间 yyMMddHHmmss

+ 3位服务节点(001 到 999)

+ N位滚动序列(000001 到 999999 长度可自定义)

重复性解决方案

+ 每秒钟单节点产生序列大于 999999 将会造成序列重复

+ 每秒钟单节点产生序列小于 999999 则不会重复.

分布式解决方案

+ 应用启动后扫描zk /sequence/${appName}/${seqName}/ 下的子节点.

+ 无节点则从001 开始创建节点.

+ 有节点则判断是否有绑定关系,有则继续使用无则新创建。

二、优点

高性能

+ 序列基本本地生成(除启动时注册zk),省去其它网络开销,数据库开销。

+ 序列批次生成,每次生成一个批次放入队列。

分布式

+ 通过zk 解决分布式问题,每台机器部署的应用生成的序列不会重复。

+ 动态节点的扩容及减少无需修改配置

缺点

+ 不支持同一个应用在同一台机器上部署多个。

+ 其它未知情况

三、使用案例

maven 引入

<dependency>

<groupId>com.system.commons</groupId>

<artifactId>commons-sequence</artifactId>

<version>1.0.0-SNAPSHOT</version>

</dependency>

环境

+ JDK 1.8

+ Zookeeper 3.5.1-alpha

依赖

+ 第三方jar依赖

<dependency>

<groupId>com.system.commons</groupId>

<artifactId>commons</artifactId>

</dependency>

<dependency>

<groupId>com.system.logback</groupId>

<artifactId>logback</artifactId>

</dependency>

<dependency>

<groupId>org.apache.zookeeper</groupId>

<artifactId>zookeeper</artifactId>

</dependency>

<dependency>

<groupId>org.apache.curator</groupId>

<artifactId>curator-recipes</artifactId>

</dependency>

Java 代码

package com.system.commons.sequence.zk;

import lombok.extern.slf4j.Slf4j;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/**
 * 序列生成工具<br/>
 *
 * 组成结构:yyMMddHHmm(可自定义) + 3位服务节点(001 到 999) + N位滚动序列(000001 到 999999 长度可自定义)<br/>
 *
 * 重复性解决方案:每分钟单节点产生序列大于 999999 将会造成序列重复,如果小于该数值则不会重复.<br/>
 *
 * 分布式解决方案:应用节点启动后扫描zookeeper sequence 节点 /sequence/${appName}/ 下的子节点
 * 如无子节点则从001 开始创建节点,如有子节点则 001 > 节点 < 已有节点最小节点值 或 已有节点最大值 > 节点 < 999
 *
 * 每个服务节点startUp 后会去zk 寻找属于自己的服务节点标志,若找不到则在/sequence/${appName}/下创建
 * 一个新的跟机器及应用绑定的服务节点标志,若找到了则使用已有的服务节点标志。
 *
 */
@Slf4j
public class SequenceFactory {

    /** 缓存已经实例化的序列化生产者 */
    private static Map<String,SequenceProducer> producerMap = new HashMap<>();

    /** 将构造函数私有化 */
    private SequenceFactory(){}

    /**
     * 获取序列化生产者
     *
     * @param zkAddress zookeeper 连接地址(ip:port)
     * @param appName   应用名称
     * @param seqName   序列名称
     * @param length    序列长度(建议至少18位,18位意味着单机并发超过999笔/秒后序列号将会重复)
     * @return          序列化生产者
     */
    public static SequenceProducer getProducer(String zkAddress,String appName,String seqName,Integer length) {

        SequenceProducer producer;

        synchronized (SequenceFactory.class) {

            producer = producerMap.get(appName + seqName);

            if (Objects.isNull(producer)) {
                producer = new SequenceProducer(appName,seqName,zkAddress,length);
                producerMap.put(appName + seqName,producer);
            }
        }

        return producer;

    }

}
package com.system.commons.sequence.zk;

import com.system.commons.utils.DateUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import java.util.Objects;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;

/**
 * 序列生成工具<br/>
 *
 * 组成结构:yyMMddHHmm + 3位服务节点(001 到 999) + N位滚动序列(000001 到 999999 长度可自定义)<br/>
 *
 * 重复性解决方案:每分钟单节点产生序列大于 999999 将会造成序列重复,如果小于该数值则不会重复.<br/>
 *
 * 分布式解决方案:应用节点启动后扫描zookeeper sequence 节点 /sequence/${appName}/${seqName}/ 下的子节点
 * 如无子节点则从001 开始创建节点,如有子节点则 001 > 节点 < 已有节点最小节点值 或 已有节点最大值 > 节点 < 999
 *
 * 每个服务节点startUp 后会去zk 寻找属于自己的服务节点标志,若找不到则在/sequence/${appName}/${seqName}/下创建
 * 一个新的跟机器及应用绑定的服务节点标志,若找到了则使用已有的服务节点标志。
 *
 */
@Slf4j
public final class SequenceProducer extends BaseSequenceProducer {

    /** 自增长序列ID */
    private static Long _currentSequence = 1L;

    /** 序列缓存队列 */
    private BlockingQueue<String> _sequenceQueue;

    /** 序列匹配应用名称 */
    private String _appName;

    /** 序列名称 */
    private String _seqName;

    /** Zookeeper 连接地址 */
    private String _zkAddress;

    /** 序列总长度 */
    private int    _length;

    /** 序列前缀格式 */
    private String _dataPattern = DateUtil.partPattern;

    /** 左补零长度 */
    private int _leftPadLength;

    /** 最大序列号 */
    private Long _maxSeq;

    /** 上一次生成凭证号的日期 */
    private String _lastGenerateTime;

    private SequenceProducer(){}

    SequenceProducer(String appName, String seqName, String zkAddress,Integer length) {

        _appName = appName;
        _seqName = seqName;
        _zkAddress = zkAddress;

        if (Objects.nonNull(length) ) {
            _length = length;
        }

        if (length < 18) {
            log.error("序列号长度小于18位是不安全的,请另行实现");
            System.exit(1);
        }

        if (length > 128) {
            log.error("序列号长度大于128位,请另行实现");
            System.exit(1);
        }

        _leftPadLength = _length - _dataPattern.length() - 3;
        _maxSeq = (long) Math.pow(100, 2) - 1;

        _sequenceQueue = new ArrayBlockingQueue<>(_maxSeq.intValue());

    }

    /**
     * 获取自定长序列 格式:yyMMddHHmm(自定义) + 3位服务节点标志(左补零) + 19位(可自定义)自增数字(左补零)
     *
     * @return          指定长度序列
     */
    public synchronized String getSequenceNo(){

        String sequence = null;

        try {

            // 当前时间
            String currentDate = DateUtil.getCurrent(_dataPattern);

            // 判断上次序列生成时间是否为空,如果为空则意味着是第一次生成(初始化生成时间)
            if(StringUtils.isBlank(_lastGenerateTime)){
                _lastGenerateTime = currentDate;
            }

            // 判断是否需要重置:重置滚动号,上次生成日期,序列池
            if(!StringUtils.isBlank(_lastGenerateTime) && !_lastGenerateTime.equals(currentDate)){
                _lastGenerateTime = currentDate;
                _sequenceQueue.clear();
                _currentSequence = 1L;
            }

            if (_sequenceQueue.isEmpty()) {
                generate();
            }

            sequence = _sequenceQueue.poll(100, TimeUnit.MILLISECONDS);

        } catch (Exception e) {
            log.error(e.getMessage(),e);
            System.exit(1);
        }

        return sequence;
    }

    /**
     * 重新生成序列
     *
     * 按既定时间重新生成序列,如:每分钟生成100万,一分钟后如果没被消耗完也会将队列里的序列清空按新的时间重新生成序列
     *
     */
    private void generate() {

        String seqNode = registerSeqNode(_zkAddress,_appName,_seqName);

        try {

            for (int i = 0; i < 1000; i++) {

                if (_currentSequence >= _maxSeq) {
                    _currentSequence = 1L;
                }

                StringBuilder sequence = new StringBuilder();

                sequence.append(_lastGenerateTime);

                sequence.append(seqNode);

                String seqNo = StringUtils.leftPad(String.valueOf(_currentSequence), _leftPadLength,"0");

                _currentSequence ++;

                sequence.append(seqNo);

                _sequenceQueue.put(sequence.toString());

            }

        } catch (Exception e) {
            log.error("生成序列号异常,系统退出...\r\n Error :{},Detail :{}",e.getMessage(),e);
            System.exit(1);
        }

    }

    /**
     * 注册序列服务节点(用于解决分布式部署生成重复序列)
     *
     * @param zkAddress     zookeeper 连接地址
     * @param appName       应用名称
     * @param seqName       序列名称
     * @return              序列服务节点001 ~ 999
     */
    private String registerSeqNode(String zkAddress, String appName, String seqName) {

        String seqNode = BaseSequenceProducer.selectSeqNode(zkAddress,appName,seqName);

        if (StringUtils.isBlank(seqNode)){
            log.error("无法选择序列节点,ZK 连接异常 !");
        }

        return seqNode;

    }

}
package com.system.commons.sequence.zk;

import com.system.commons.sequence.zk.utils.NodeSelector;
import org.apache.commons.lang3.StringUtils;

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

/**
 * 简介
 *
 */
public class BaseSequenceProducer {

    private static NodeSelector nodeSelector;

    private static Map<String,String> seqNodeMap = new HashMap<>();

    public static synchronized String selectSeqNode(String zkAddress, String appName, String seqName){

        String seqNode = seqNodeMap.get(appName + seqName);

        if (StringUtils.isNoneBlank(seqNode)) {
            return seqNode;
        }

        if (Objects.isNull(nodeSelector)){
            nodeSelector = new NodeSelector();
        }

        seqNode = nodeSelector.generateServerNode(zkAddress,appName,seqName);

        seqNodeMap.put(appName + seqName,seqNode);

        return seqNode;

    }

}
package com.system.commons.sequence.zk.utils;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.imps.CuratorFrameworkState;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.zookeeper.CreateMode;
import java.util.List;

/**
 * 节点选择器<br/>
 *
 */
@Slf4j
public class NodeSelector {

    /** zookeeper 客户端连接 */
    private static CuratorFramework client = null;

    /** zookeeper 连接字符串 */
    private static String connectString;

    /** zookeeper 序列节点名称 */
    private static final String PATH = "/sequence";

    /** 应用名称 */
    private String applicationName;

    /** 序列名称 */
    private String sequenceName;

    /** zookeeper 连接超时时间 */
    private static final int _connection_timeout = 1000 *  10;

    /** Session 超时时间(一周,为了防止网络抖动节点被重复使用) */
    private static final int _session_timeout = 1000 * 60 * 60 * 24 * 7;

    /** zookeeper 连接重试最大次数 */
    private static final int _max_retry_times = 10;

    /** zookeeper 连接重试间隔休眠时间 */
    private static final int _retry_sleep_times = 1000 * 30;

    private static String localIp = IPHelper.getLocalIP();

    /**
     * 生成服务节点
     *
     * @param connectString     zookeeper连接字符串
     * @param applicationName   应用名称
     * @param sequenceName      序列名称
     * @return                  服务节点
     */
    public String generateServerNode (String connectString,String applicationName,String sequenceName){

        if (StringUtils.isBlank(connectString)){
            log.error("zookeeper 连接地址为空,系统异常退出.");
            System.exit(1);
        }
        if (StringUtils.isBlank(applicationName)) {
            log.error("应用名称为空,系统异常退出.");
            System.exit(1);
        }

        if (StringUtils.isBlank(NodeSelector.connectString)){
            NodeSelector.connectString = connectString;
        }

        this.applicationName = applicationName;
        this.sequenceName = sequenceName;

        synchronized (NodeSelector.class){
            connectZookeeper();
        }

        return lockAndSelectNode();

    }

    /**
     * 连接zookeeper服务
     */
    private void connectZookeeper(){

        try{

            if (null == client || !CuratorFrameworkState.STARTED.equals(client.getState())){

                client = createSimple();

                client = createWithOptions(
                        connectString,
                        new ExponentialBackoffRetry(_retry_sleep_times, _max_retry_times),
                        _connection_timeout,
                        _session_timeout
                );

                client.start();

            }

            createRootNode();

            createAppNode();

            createSeqNode();

        } catch (Exception e){
            log.error(e.getMessage(),e);
            System.exit(1);
        }
    }

    private void createRootNode() throws Exception{
        if (null == client.checkExists().forPath(PATH)){
            client.create()
                    .withMode(CreateMode.PERSISTENT)
                    .forPath(PATH, "project sequence node".getBytes());
        }
    }

    private void createAppNode() throws Exception{
        String pathData = "project [" + applicationName + "] sequence";
        if (null == client.checkExists().forPath(PATH + "/" + applicationName)){
            client.create()
                    .withMode(CreateMode.PERSISTENT)
                    .forPath(PATH + "/" + applicationName, pathData.getBytes());
        }
    }

    private void createSeqNode() throws Exception{
        if (null == client.checkExists().forPath(PATH + "/" + applicationName + "/" + sequenceName)){
            client.create()
                    .withMode(CreateMode.PERSISTENT)
                    .forPath(PATH + "/" + applicationName + "/" + sequenceName);
        }
    }

    /**
     * 锁定并生成服务节点
     *
     * @return  服务节点
     */
    private String lockAndSelectNode(){
        try {
            String node = selectNode();
            if (StringUtils.isBlank(node)){
                throw new NullPointerException("selectNode return null");
            }

            if ( null == client.checkExists().forPath(PATH + "/" + applicationName + "/" + sequenceName + "/" + node)){
                client.create()
                        .withMode(CreateMode.PERSISTENT)
                        .forPath(PATH + "/" + applicationName + "/" + sequenceName + "/" + node,localIp.getBytes());
            }

            return node;
        } catch (Exception e){
            log.error(e.getMessage(),e);
            System.exit(1);
        }
        return null;
    }

    private CuratorFramework createSimple() {
        ExponentialBackoffRetry retryPolicy = new ExponentialBackoffRetry(1000, 3);
        return CuratorFrameworkFactory.newClient(NodeSelector.connectString, retryPolicy);
    }

    private CuratorFramework createWithOptions(String connectionString,
                                                     RetryPolicy retryPolicy,
                                                     int connectionTimeoutMs,
                                                     int sessionTimeoutMs) {
        return CuratorFrameworkFactory
                .builder()
                .connectString(connectionString)
                .retryPolicy(retryPolicy)
                .connectionTimeoutMs(connectionTimeoutMs)
                .sessionTimeoutMs(sessionTimeoutMs)
                .build();
    }

    /***
     * 选择节点
     *
     * @return  服务节点
     */
    private String selectNode(){

        synchronized (NodeSelector.class){

            try{

                List<String> list = client.getChildren().forPath(PATH + "/" + applicationName + "/" + sequenceName);

                int minNodeData = 1;
                if (list.isEmpty()){
                    return StringUtils.leftPad(String.valueOf(minNodeData),3,"0");
                }

                int [] pathDataArr = new int [list.size()];

                int position = 0;
                for (String path : list){

                    pathDataArr[position] = Integer.valueOf(path);
                    position ++;

                    String data = new String(client.getData().forPath(PATH + "/" + applicationName + "/" + sequenceName + "/"+path));

                    if (!data.equals("") && data.equals(localIp)){
                        return StringUtils.leftPad(path,3,"0");
                    }

                }

                sort(pathDataArr);

                int node = pathDataArr[0] > minNodeData ? pathDataArr[0] - 1 : pathDataArr[pathDataArr.length -1] + 1;

                return StringUtils.leftPad(String.valueOf(node),3,"0");

            } catch (Exception e) {
                log.error(e.getMessage(),e);
            }

        }

        return null;
    }

    /**
     * 冒泡排序
     *
     * @param arr   需要排序的数组
     */
    private static void sort(int [] arr){
        for(int i = 0 ; i < arr.length-1 ; i++){
            for(int j = i+1 ; j < arr.length ; j++){
                int temp ;
                if(arr[i] > arr[j]){
                    temp = arr[j];
                    arr[j] = arr[i];
                    arr[i] = temp;
                }
            }
        }
    }

}
package com.system.commons.sequence.zk.utils;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.Enumeration;

/**
 * IP获取帮助类
 *
 *
 */
@Slf4j
public class IPHelper {

    private static volatile String IP_ADDRESS = "";
    private static final String LOCAL_IP = "127.0.0.1";

    /**
     * 获取本地IP
     *
     * @return IP地址
     */
    public static String getLocalIP() {
        if (StringUtils.isNotBlank(IP_ADDRESS)) {
            return IP_ADDRESS;
        }
        try {
            Enumeration allNetInterfaces = NetworkInterface.getNetworkInterfaces();
            InetAddress ip;
            while (allNetInterfaces.hasMoreElements()) {
                NetworkInterface netInterface = (NetworkInterface) allNetInterfaces.nextElement();
                Enumeration addresses = netInterface.getInetAddresses();
                while (addresses.hasMoreElements()) {
                    ip = (InetAddress) addresses.nextElement();
                    if (ip != null && ip instanceof Inet4Address) {
                        String tip = ip.getHostAddress();
                        if(LOCAL_IP.equals(tip)){
                            continue;
                        }
                        IP_ADDRESS = tip;
                        return IP_ADDRESS;
                    }
                }
            }
        } catch (SocketException e) {
            log.error("获取本机IP Socket异常:{}", e);
        }catch (Exception e) {
            log.error("获取本机IP异常:{}", e);
        }
        return LOCAL_IP;
    }

}
package com.system.commons.sequence.redis.impl;

import com.google.common.base.Strings;
import com.system.commons.sequence.redis.SequenceFacade;
import com.system.commons.sequence.redis.utils.SeqRedisManager;
import lombok.extern.slf4j.Slf4j;
import org.joda.time.DateTime;

/**
 * 获取序列
 *
 * <p>
 *     1 获取32位数字序列
 *     2 获取定长序列
 * </p>
 */
@Slf4j
public class SequenceImpl extends SeqRedisManager implements SequenceFacade {

    /**
     * 日期格式:yyyyMMdd
     */
    public static final String datePattern = "yyyyMMdd";

    /**
     * 32长序列key
     */
    public static final String LENGTH_32_KEY = "SEQUENCE:LENGTH_32_KEY";

    /**
     * 自定义长序列key
     */
    public static final String CUSTOM_KEY = "SEQUENCE:CUSTOM_KEY:";

    /**
     * 获取32位数字序列
     *
     * @return 32位长序列
     */
    @Override
    public String getUniqueSeq() {
        Long num = autoIncrement(LENGTH_32_KEY);
        String seq = DateTime.now().toString(datePattern) + Strings.padStart(String.valueOf(num), 24, ‘0‘);
        log.debug("32位序列:{}",seq);
        return seq;
    }

    /**
     * 获取定长序列
     * 默认从0开始,超过指定长度后从0开始循环
     *
     * @param key    key
     * @param length 长度
     * @return 指定长度序列
     */
    @Override
    public synchronized String  getSeqByKey(String key, int length) {
        Long num = autoIncrement(CUSTOM_KEY+key);
        if(String.valueOf(num).length() > length){
            num = autoIncrementBy(CUSTOM_KEY+key,-num);
        }
        String seq = Strings.padStart(String.valueOf(num), length, ‘0‘);
        log.debug("自定义长度序列:{}",seq);
        return seq;
    }
}
package com.system.commons.sequence.redis.utils;

import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisSentinelConfiguration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.StringRedisTemplate;
import redis.clients.jedis.JedisPoolConfig;

/**
 * redis实现
 *
 * <p>
 *      1、redis 自增长
 *      2、redis 增长定值
 * </p>
 */
@Slf4j
public class SeqRedisManager {

    /**
     * 用户
     */
    private static final String hostName = "requirepass" ;

    /**
     * 密码
     */
    private static final String pwd = "[email protected]" ;

    /**
     * 实例配置
     */
    @Setter
    public RedisSentinelConfiguration redisSentinelConfiguration;

    /**
     * 基础配置
     */
    @Setter
    public JedisPoolConfig jedisPoolConfig;

    /**
     * redisTemplate
     */
    public StringRedisTemplate redisTemplate;

    /**
     * 初始化用户\密码
     */
    public void init(){
        JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory(redisSentinelConfiguration);
        jedisConnectionFactory.setHostName(hostName);
        jedisConnectionFactory.setPassword(pwd);
        jedisConnectionFactory.setPoolConfig(jedisPoolConfig);
        jedisConnectionFactory.afterPropertiesSet();

        redisTemplate = new StringRedisTemplate(jedisConnectionFactory);
        log.info("序列初始化完成");
    }

    /**
     * redis 自增长
     *
     * @param keyEnum 关键字
     */
    public Long autoIncrement(final String keyEnum) {

        Long result = redisTemplate.execute((RedisConnection connection) -> {
            byte[] redisKey = redisTemplate.getStringSerializer().serialize(keyEnum);
            Long num = connection.incr(redisKey);
            log.debug("keyEnum:{} num:{}", keyEnum, num);
            return num;
        });

        log.debug("incr response:{}", result);
        return result;
    }

    /**
     * redis 增长定值
     *
     * @param keyEnum 关键字
     * @param incrementBy 增长值
     */
    public Long autoIncrementBy(final String keyEnum,long incrementBy) {

        Long result = redisTemplate.execute((RedisConnection connection) -> {
            byte[] redisKey = redisTemplate.getStringSerializer().serialize(keyEnum);
            Long num = connection.incrBy(redisKey, incrementBy);
            log.debug("keyEnum:{} num:{},incrementBy:{}",keyEnum,num,incrementBy);
            return num;
        });

        log.debug("incrBy response:{}", result);
        return result;
    }

}
package com.system.commons.sequence.redis;

/**
 * 获取序列
 *
 * <p>
 *     1 获取32位数字序列
 *     2 获取定长序列
 * </p>
 */
public interface SequenceFacade {

    /**
     * 获取32位数字序列
     *
     * @return      32位长序列
     */
    String getUniqueSeq();

    /**
     * 获取定长序列
     *      默认从0开始,超过指定长度后从0开始循环(可能出现全是0的序列)
     *
     * @param key       key
     * @param length    长度
     * @return          指定长度序列
     */
    String getSeqByKey(String key,int length);
}
package com.system.commons.sequence;

import com.system.commons.sequence.zk.SequenceFactory;
import com.system.commons.sequence.zk.SequenceProducer;

/**
* 序列号测试类
*/
public class Test {

    public static void main(String[] args) throws InterruptedException {

        String zkAddress = "10.0.21.56:2181";

       for (int j = 0 ; j < 10 ; j ++) {        
            new Thread (()->{
                SequenceProducer producer = SequenceFactory.getProducer(zkAddress,"account","receipt",32);
                System.out.println("receiptNo => " +producer.getSequenceNo());
            }).start();

            new Thread (()->{
                SequenceProducer producer = SequenceFactory.getProducer(zkAddress,"account","test",18);
                System.out.println("testNo =>" + producer.getSequenceNo());

            }).start();

        }

        Thread.sleep(1000 * 60 * 60);

    }

}
时间: 2024-10-15 13:48:36

zookeeper+redies 系统序列号生成的相关文章

关于后台系统自动生成的一点思考

大量实践发现后台管理程序,其实90%的代码都是相同的,当然是在抛弃复杂逻辑业务的情况下,那么如何能高效的节约这些时间呢,那就是接下来我要说的,对于后台系统自动生成的一些思考. 适用情景: 1.表编号id为自增(基于现在大部分表编号都是自增的情况): 2.没有太复杂业务关联关系,比如表的某一个字段,存储了一个json对象,为了平衡后台用户使用,需要友好的分段展示给用户的定制ui界面:还比如表中存储了外键的多个id,但为了方便用户使用,只能已标签name的方式,给用户展示,等等这些超强业务黏合逻辑的

Mac系统Git生成ssh公钥

Mac系统Git生成ssh公钥 在使用Git仓库进行代码管理时,新的电脑上往往需要生成ssh公钥进行匹配,Mac系统生成Git公钥过程如下: 1.检查本机是否已有公钥 在终端中输入如下命令: ? 1 $ cd ~/.ssh 2.如果电脑中有以前遗留的密钥,将其删除掉 使用如下命令: ? 1 2 3 $ mkdir key_backup $ cp id_rsa* key_backup $ rm id_rsa* 3.生成新的公钥 终端中输入如下命令 ? 1 $ ssh-keygen -t rsa -

ORACLE实现自定义序列号生成

实际工作中,难免会遇到序列号生成问题,下面就是一个简单的序列号生成函数 (1)创建自定义序列号配置表如下: --自定义序列 create table S_AUTOCODE ( pk1 VARCHAR2(32) primary key, atype VARCHAR2(20) not null, owner VARCHAR2(10) not null, initcycle CHAR(1) not null, cur_sernum VARCHAR2(50) not null, zero_flg VAR

系统中生成编号/单号问题的实现方案讨论

应用场景 场景:对于大多数电商系统或财务系统来说,系统中的单号一般都不是从1开始的自增数字,而是一串有一定意义的字符串序列. 而往往这样的单号是要全局唯一的,不可重复. 那么,每次新增订单记录时,这个单号就要按照指定的规则来生成. 常见的订单号规则是字母前缀+日期+时间+定长的数字,如DD201610201559060001(format:XXYYYYMMDDHHmmssNNNN),PZ16102015000012(format:XXYYMMDDHHNNNNNN) 常见方案... 实现这样的场景

唯一序列号生成,自測支持高并发,不支持集群

序列号组成:前缀 + 日期 + 尾数 比如:ZC20140806000001 总共两个组成:一个枚举类.一个静态生成器.另外须要设计自己主动任务每日凌晨或其它时间重置一次尾数. 先上枚举类: package com.boxmeal.base.constant.common; import java.util.concurrent.atomic.AtomicInteger; /** * 序列号生成枚举 * @author bling * */ public enum SNEnum { // 订单编

zookeeper全局唯一id生成

一背景 传统生成id方式可以靠数据库的自增来实现,但是在分布式环境下不太适应.依赖数据库容易造成单点. 为什么不用UUID的,网上看别人介绍的时候,从两个方面去分析: 1 大并发的情况下,UUID会出现重复. 2.UUID是随即的,含义不明.从业务角度去考虑,如果用作订单,用户查询订单在数据分片的情况下很可能分散在多个库,查询困难. 全局唯一id的要求比较高: 不能有单点故障. 性能好,毫秒级返回. 能顺序便于DB存储及划分. 二 使用zookeeper生成全局唯一id. 2.1 利用Zooke

【python】13位随机序列号生成工具 源码分析

By Dolphin,BeiJing,20150712 0X00  背景 最近在学习python 这门语言,刚学完for循环,对于很多语句语法都不太熟悉.就在今天,看到有某个网站的活动,需要输入一个13位的序列号来判断你是否中奖,但是这个13位序列号是需要购买他们家的产品才能获得,得耗费一定的金钱,于是我就在想,是不是能自己写一个序列号生成器来碰碰运气,所以决定运用刚学的python的初级知识进行编写. 0X01  知识点准备 这个工具主要的功能是生成随机字母做序列号,python中的rando

java RSA加密生成license,用CPU序列号生成机器码

? license里就是一些注册信息,键值对组成的字符串 ? 对称加密: DES,AES,加密解密都用一个秘钥,速度快 非对称机密 RSA,可以私钥加密公钥解密,也可以公钥机密私钥解密,速度慢 注意:RSA加密明文最大长度117字节,解密要求密文最大长度为128字节,所以在加密和解密的过程中需要分块进行.RSA加密对明文的长度是有限制的,如果加密数据过大会抛出异常: ? 常见加密算法 DES? ??? DES是Data Encryption Standard(数据加密标准)的缩写,DES算法为密

使用Python定时执行一任务,自动登录某web系统,生成报表,然后发送邮件给指定人员

一.项目需求 每周从A系统生成一张Excel报表,发送此报表给指定人员,相关人员依据此报表去完成后续的工作. 项目限制: 1.无法通过EDI系统交互的方式从后台读取数据 2.由于公司网络环境限制,不能使用SMTP发送邮件,比如,不能通过smtp.163.com发送邮件 二.解决方案 模拟人工操作,登录系统,输入相应查询条件,生成报表,保存后发送邮件给指定人员. 采用技术:采用Python 三.关键点 1.使用selenium模拟登录浏览器 '使用IE浏览器 driver.webdriver.Ie