分布式ID,调度和session共享问题

这是我参与11月更文挑战的第17天,活动详情查看:2021最后一次更文挑战

分布式ID

UUID

通用唯一识别码, 长度长,产生重复并造成错误的情况非常低, 没有什么规律, 不适合建立索引。

UUID.randomUUID();
复制代码

数据库

独立数据库创建一个表,这个表的ID设置自增, 需要ID的时候,这个表模拟自增。使用last_insert_id()就可以获取到。 多了一个数据库,还需要连接, 对性能和稳定性都有问题

雪花算法

生成的ID是Long型,1位是符号位,41位是时间戳, 10位是机器ID, 12位是序号。

public class SnowflakeSequence implements Sequence {

   /**
    * 开始时间截 (2021-11-17 19:44:8)
    */
   private final long twepoch = 1637149448L;

   /**
    * 机器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;

   @Override
   public synchronized long nextValue() throws SeqException {
      long timestamp = timeGen();

      // 如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常
      if (timestamp < lastTimestamp) {
         throw new SeqException("[SnowflakeSequence-nextValue] 当前时间小于上次生成序列号的时间,时间被回退了,请确认服务器时间的设置.");
      }

      // 如果是同一时间生成的,则进行毫秒内序列
      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 当前时间戳
    */
   private long tilNextMillis(long lastTimestamp) {
      long timestamp = timeGen();
      while (timestamp <= lastTimestamp) {
         timestamp = timeGen();
      }
      return timestamp;
   }

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

   public void setWorkerId(long workerId) {
      if (workerId > maxWorkerId) {
         throw new SeqException("[SnowflakeSequence-setWorkerId] workerId 不能大于31.");
      }

      this.workerId = workerId;
   }

   public void setDatacenterId(long datacenterId) {
      if (datacenterId > maxDatacenterId) {
         throw new SeqException("[SnowflakeSequence-setDatacenterId] datacenterId 不能大于31.");
      }

      this.datacenterId = datacenterId;
   }

   /**
    * 下一个生成序号(带格式)
    * @return
    * @throws SeqException
    */
   @Override
   public String nextNo() throws SeqException {
      return String.valueOf(nextValue());
   }

}
复制代码

Redis

使用incr命令

分布式调度-Elastic-job

定时任务与消息队列的区别

共同点

  • 异步处理
  • 应用解耦
  • 流量削峰

不同点

定时任务是时间驱动,倾向批处理, MQ是事件驱动,倾向逐条处理。

Elastic-Job

基于quartz二次开发, 由Elastic-Job-Lite和Elastic-Job-Cloud组成。定位是轻量级无中心化解决方案。基于ZK存储和通知。

public class Job implements SimpleJob{
    public void execute(ShardingContext context){
        // 定时任务的业务
    }
}

public static void main(String[] args){
    ZookeeperConfiguration zk = new ZookeeperConfiguration("localhost:2181", "namespace");
    CoordinatorRegistryCenter cr = new CoordinatorRegistryCenter(zk);
    cr.init();
    
    JobCoreConfiguration conf = JobCoreConfiguration.newBuilder("name", "*/2 * * * * ?", 1).build();
	SimpleJobConfiguration simple = new SimpleJobConfiguration(conf, Job.class.getName());
    JobSchedule jobSchedule = new JobScheduler(cr, LiteJobConfiguration.newBuilder(simple).build());
	jobSchedule.init();
}
复制代码

分片

public class Job implements SimpleJob{
    public void execute(ShardingContext context){
        int item = shardingContext.getShardingItem();
		String param = shardingContext.getShardingParameter();
    }
}

public static void main(String[] args){
    ZookeeperConfiguration zk = new ZookeeperConfiguration("localhost:2181", "namespace");
    CoordinatorRegistryCenter cr = new CoordinatorRegistryCenter(zk);
    cr.init();
    
    JobCoreConfiguration conf = JobCoreConfiguration.newBuilder("name", "*/2 * * * * ?", 3).shardingItemParameters("0=red,1=blue").build();
	SimpleJobConfiguration simple = new SimpleJobConfiguration(conf, Job.class.getName());
    JobSchedule jobSchedule = new JobScheduler(cr, LiteJobConfiguration.newBuilder(simple).build());
	jobSchedule.init();
}
复制代码

主要在于代码JobCoreConfiguration.newBuilder("name", "*/2 * * * * ?", 3).shardingItemParameters("0=red,1=blue").build()。 在job处调用String param = shardingContext.getShardingParameter();。 每次启动Elastic-job项目会默认计算分片存储到ZK。

Sesson共享

解决session一致性的方案

  • Nginx的IP_HASH策略

    会话粘滞。优点是配置简单,不入侵应用。缺点是服务器重启session丢失, 单点故障, 单点负载可能过高。

  • Session复制

    多个tomcat之间修改配置文件进行session复制,这是早期的方案,现在不再推荐。优点是不入侵应用, 服务器可以水平扩展, 能适应各种负载均衡, 重启或宕机不会造成session丢失。缺点是性能低, 内存消耗高, 不能存储过多数据, 延迟性高。

  • session共享, 集中存储

    session存储到redis,优点是能适应各种负载均衡, 重启或宕机不会造成session丢失, 扩展能力强,适合大集群, 缺点是对应用有入侵。

Guess you like

Origin juejin.im/post/7031503678003478559