6位顺序号生成_分布式id生成策略,我和面试官扯了一个半小时

一、分布式系统带来ID生成挑战

在分布式系统中,往往需要对大量的数据如订单、账户进行标识,以一个有意义的有序的序列号来作为全局唯一的ID。

而分布式系统中我们对ID生成器要求又有哪些呢?

  • 全局唯一性:不能出现重复的ID号,既然是唯一标识,这是最基本的要求。

  • 递增:比较低要求的条件为趋势递增,即保证下一个ID一定大于上一个ID,而比较苛刻的要求是连续递增,如1,2,3等等。

  • 高可用高性能:ID生成事关重大,一旦挂掉系统崩溃;高性能是指必须要在压测下表现良好,如果达不到要求则在高并发环境下依然会导致系统瘫痪。

二、业内方案简介

1. UUID方案

优点:

  1. 能够保证独立性,程序可以在不同的数据库间迁移,效果不受影响。

  2. 保证生成的ID不仅是表独立的,而且是库独立的,这点在你想切分数据库的时候尤为重要。

缺点:

  1. 性能问题:UUID太长,通常以36长度的字符串表示,对MySQL索引不利:如果作为数据库主键,在InnoDB引擎下,UUID的无序性可能会引起数据位置频繁变动,严重影响性能。

  2. UUID无业务含义:很多需要ID能标识业务含义的地方不使用。

  3. 不满足递增要求。

2. snowflake方案

snowflake是twitter开源的分布式ID生成系统。 Twitter每秒有数十万条消息的请求,每条消息都必须分配一条唯一的id,这些id还需要一些大致的顺序(方便客户端排序),并且在分布式系统中不同机器产生的id必须不同。

5149422051a5b0fec57844334b1060c2.png

snowflake的结构如下(每部分用-分开):

0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 – 000000000000

第一位为未使用,接下来的41位为毫秒级时间(41位的长度可以使用69年),然后是5位datacenterId和5位workerId(10位的长度最多支持部署1024个节点) ,最后12位是毫秒内的计数(12位的计数顺序号支持每个节点每毫秒产生4096个ID序号)

一共加起来刚好64位,为一个Long型。

snowflake生成的ID整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞(由datacenter和workerId作区分),并且效率较高。snowflake的缺点是:

  1. 强依赖时钟,如果主机时间回拨,则会造成重复ID

  2. ID虽然有序,但是不连续

snowflake现在有较好的改良方案,比如美团点评开源的分布式ID框架:leaf,通过使用ZooKeeper解决了时钟依赖问题。

3. 基于数据库方案

利用数据库生成ID是最常见的方案。能够确保ID全数据库唯一。其优缺点如下:

优点:

  1. 非常简单,利用现有数据库系统的功能实现,成本小,有DBA专业维护。

  2. ID单调自增。

缺点:

  1. 不同数据库语法和实现不同,数据库迁移的时候或多数据库版本支持的时候需要处理。

  2. 在单个数据库或读写分离或一主多从的情况下,只有一个主库可以生成。有单点故障的风险。

  3. 在性能达不到要求的情况下,比较难于扩展。

  4. 如果涉及多个系统需要合并或者数据迁移会比较麻烦。

  5. 分表分库的时候会有麻烦。

4.其他方案简介

通过Redis生成ID(主要通过redis的自增函数)、ZooKeeper生成ID、MongoDB的ObjectID等均可实现唯一性的要求。

三、我们在实际应用中使用的方案

1. 方案简介

实际业务中,除了分布式ID全局唯一之外,还有是否趋势/连续递增的要求。根据具体业务需求的不同,有两种可选方案。

一是只保证全局唯一,不保证连续递增。二是既保证全局唯一,又保证连续递增。

2. 基于ZooKeeper和本地缓存的方案

基于zookeeper分布式ID实现方案有很多种,本方案只使用ZooKeeper作为分段节点协调工具。每台服务器首先从zookeeper缓存一段,如1-1000的id。

此时zk上保存最大值1000,每次获取的时候都会进行判断,如果id小于本地最大值,即id<=1000,则更新本地的当前值,如果id大于本地当前值,比如说是1001,则会将从zk再获取下一个id数据段并在本地缓存。获取数据段的时候需要更新zk节点数据,更新的时候使用curator的分布式锁来实现。

由于id是从本机获取,因此本方案的优点是性能非常好。缺点是如果多主机负载均衡,则会出现不连续的id,当然将递增区段设置为1也能保证连续的id,但是效率会受到很大影响。

实现关键源码如下:

import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.locks.InterProcessSemaphoreMutex;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.data.Stat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.UnsupportedEncodingException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
* 根据开源项目mycat实现基于zookeeper的递增序列号
*


* 只要配置好ZK地址和表名的如下属性
* MINID 某线程当前区间内最小值
* MAXID 某线程当前区间内最大值
* CURID 某线程当前区间内当前值
*
* @author wangwanbin
* @version 1.0
* @time 2017/9/1
*/
public class ZKCachedSequenceHandler extends SequenceHandler {
   protected static final Logger LOGGER = LoggerFactory.getLogger(ZKCachedSequenceHandler.class);
   private static final String KEY_MIN_NAME = ".MINID";// 1
   private static final String KEY_MAX_NAME = ".MAXID";// 10000
   private static final String KEY_CUR_NAME = ".CURID";// 888
   private final static long PERIOD = 1000;//每次缓存的ID段数量
   private static ZKCachedSequenceHandler instance = new ZKCachedSequenceHandler();
   /**
    * 私有化构造方法,单例模式
    */
   private ZKCachedSequenceHandler() {
   }
   /**
    * 获取sequence工具对象的唯一方法
    *
    * @return
    */
   public static ZKCachedSequenceHandler getInstance() {
       return instance;
   }
   private Map> tableParaValMap = null;
   private CuratorFramework client;
   private InterProcessSemaphoreMutex interProcessSemaphore = null;
   public void loadZK() {
       try {
           this.client = CuratorFrameworkFactory.newClient(zkAddress, new ExponentialBackoffRetry(1000, 3));
           this.client.start();
       } catch (Exception e) {
           LOGGER.error("Error caught while initializing ZK:" + e.getCause());
       }
   }
   public Map getParaValMap(String prefixName) {
       if (tableParaValMap == null) {
           try {
               loadZK();
               fetchNextPeriod(prefixName);
           } catch (Exception e) {
               LOGGER.error("Error caught while loding configuration within current thread:" + e.getCause());
           }
       }
       Map paraValMap = tableParaValMap.get(prefixName);
       return paraValMap;
   }
   public Boolean fetchNextPeriod(String prefixName) {
       try {
           Stat stat = this.client.checkExists().forPath(PATH + "/" + prefixName + SEQ);
           if (stat == null || (stat.getDataLength() == 0)) {
               try {
                   client.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT)
                           .forPath(PATH + "/" + prefixName + SEQ, String.valueOf(0).getBytes());
               } catch (Exception e) {
                   LOGGER.debug("Node exists! Maybe other instance is initializing!");
               }
           }
           if (interProcessSemaphore == null) {
               interProcessSemaphore = new InterProcessSemaphoreMutex(client, PATH + "/" + prefixName + SEQ);
           }
           interProcessSemaphore.acquire();
           if (tableParaValMap == null) {
               tableParaValMap = new ConcurrentHashMap<>();
           }
           Map paraValMap = tableParaValMap.get(prefixName);
           if (paraValMap == null) {
               paraValMap = new ConcurrentHashMap<>();
               tableParaValMap.put(prefixName, paraValMap);
           }
           long now = Long.parseLong(new String(client.getData().forPath(PATH + "/" + prefixName + SEQ)));
           client.setData().forPath(PATH + "/" + prefixName + SEQ, ((now + PERIOD) + "").getBytes());
           if (now == 1) {
               paraValMap.put(prefixName + KEY_MAX_NAME, PERIOD + "");
               paraValMap.put(prefixName + KEY_MIN_NAME, "1");
               paraValMap.put(prefixName + KEY_CUR_NAME, "0");
           } else {
               paraValMap.put(prefixName + KEY_MAX_NAME, (now + PERIOD) + "");
               paraValMap.put(prefixName + KEY_MIN_NAME, (now) + "");
               paraValMap.put(prefixName + KEY_CUR_NAME, (now) + "");
           }
       } catch (Exception e) {
           LOGGER.error("Error caught while updating period from ZK:" + e.getCause());
       } finally {
           try {
               interProcessSemaphore.release();
           } catch (Exception e) {
               LOGGER.error("Error caught while realeasing distributed lock" + e.getCause());
           }
       }
       return true;
   }
   public Boolean updateCURIDVal(String prefixName, Long val) {
       Map paraValMap = tableParaValMap.get(prefixName);
       if (paraValMap == null) {
           throw new IllegalStateException("ZKCachedSequenceHandler should be loaded first!");
       }
       paraValMap.put(prefixName + KEY_CUR_NAME, val + "");
       return true;
   }
   /**
    * 获取自增ID
    *
    * @param sequenceEnum
    * @return
    */
   @Override
   public synchronized long nextId(SequenceEnum sequenceEnum) {
       String prefixName = sequenceEnum.getCode();
       Map paraMap = this.getParaValMap(prefixName);
       if (null == paraMap) {
           throw new RuntimeException("fetch Param Values error.");
       }
       Long nextId = Long.parseLong(paraMap.get(prefixName + KEY_CUR_NAME)) + 1;
       Long maxId = Long.parseLong(paraMap.get(prefixName + KEY_MAX_NAME));
       if (nextId > maxId) {
           fetchNextPeriod(prefixName);
           return nextId(sequenceEnum);
       }
       updateCURIDVal(prefixName, nextId);
       return nextId.longValue();
   }
   public static void main(String[] args) throws UnsupportedEncodingException {
       long startTime = System.currentTimeMillis();   //获取开始时间
       final ZKCachedSequenceHandler sequenceHandler = getInstance();
       sequenceHandler.loadZK();
       new Thread() {
           public void run() {
               long startTime2 = System.currentTimeMillis();   //获取开始时间
               for (int i = 0; i < 5000; i++) {
                   System.out.println("线程1 " + sequenceHandler.nextId(SequenceEnum.ACCOUNT));
               }
               long endTime2 = System.currentTimeMillis(); //获取结束时间
              System.out.println("程序运行时间1:" + (endTime2 - startTime2) + "ms");
           }
       }.start();
       for (int i = 0; i < 5000; i++) {
           System.out.println("线程2 " + sequenceHandler.nextId(SequenceEnum.ACCOUNT));
       }
       long endTime = System.currentTimeMillis(); //获取结束时间
      System.out.println("程序运行时间2:" + (endTime - startTime) + "ms");
   }
}

可以看到,由于不需要进行过多的网络消耗,缓存式的zk协调方案性能相当了得,生成10000个id仅需553ms(两个线程耗时较长者) , 平均每个id消耗0.05ms。

 3.利用zk的永久自增节点策略实现持续递增ID

使用zk的永久sequence策略创建节点,并获取返回值,然后删除前一个节点,这样既防止zk服务器存在过多的节点,又提高了效率;节点删除采用线程池来统一处理,提高响应速度。

优点:能创建连续递增的ID。

关键实现代码如下:

package com.zb.p2p.utils;

import com.zb.p2p.enums.SequenceEnum;
import org.apache.commons.pool2.PooledObject;
import org.apache.commons.pool2.PooledObjectFactory;
import org.apache.commons.pool2.impl.DefaultPooledObject;
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.zookeeper.CreateMode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayDeque;
import java.util.Iterator;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
* 基于zk的永久型自增节点PERSISTENT_SEQUENTIAL实现
* 每次生成节点后会使用线程池执行删除节点任务
* Created by wangwanbin on 2017/9/5.
*/
public class ZKIncreaseSequenceHandler extends SequenceHandler implements PooledObjectFactory {
   protected static final Logger LOGGER = LoggerFactory.getLogger(ZKCachedSequenceHandler.class);
   private static ZKIncreaseSequenceHandler instance = new ZKIncreaseSequenceHandler();
   private static ExecutorService fixedThreadPool = Executors.newFixedThreadPool(1);
   private GenericObjectPool genericObjectPool;
   private Queue preNodes = new ConcurrentLinkedQueue<>();
   private static String ZK_ADDRESS = ""; //192.168.0.65
   private static String PATH = "";//  /sequence/p2p
   private static String SEQ = "";//seq;
   /**
    * 私有化构造方法,单例模式
    */
   private ZKIncreaseSequenceHandler() {
       GenericObjectPoolConfig config = new GenericObjectPoolConfig();
       config.setMaxTotal(4);
       genericObjectPool = new GenericObjectPool(this, config);
   }
   /**
    * 获取sequence工具对象的唯一方法
    *
    * @return
    */
   public static ZKIncreaseSequenceHandler getInstance(String zkAddress, String path, String seq) {
       ZK_ADDRESS = zkAddress;
       PATH = path;
       SEQ = seq;
       return instance;
   }
   @Override
   public long nextId(final SequenceEnum sequenceEnum) {
       String result = createNode(sequenceEnum.getCode());
       final String idstr = result.substring((PATH + "/" + sequenceEnum.getCode() + "/" + SEQ).length());
       final long id = Long.parseLong(idstr);
       preNodes.add(id);
       //删除上一个节点
       fixedThreadPool.execute(new Runnable() {
           @Override
           public void run() {
               Iterator iterator = preNodes.iterator();
               if (iterator.hasNext()) {
                   long preNode = iterator.next();
                   if (preNode < id) {
                       final String format = "%0" + idstr.length() + "d";
                       String preIdstr = String.format(format, preNode);
                       final String prePath = PATH + "/" + sequenceEnum.getCode() + "/" + SEQ + preIdstr;
                       CuratorFramework client = null;
                       try {
                           client = (CuratorFramework) genericObjectPool.borrowObject();
                           client.delete().forPath(prePath);
                           preNodes.remove(preNode);
                       } catch (Exception e) {
                           LOGGER.error("delete preNode error", e);
                       } finally {
                           if (client != null)
                               genericObjectPool.returnObject(client);
                       }
                   }
               }
           }
       });
       return id;
   }
   private String createNode(String prefixName) {
       CuratorFramework client = null;
       try {
           client = (CuratorFramework) genericObjectPool.borrowObject();
           String result = client.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT_SEQUENTIAL)
                   .forPath(PATH + "/" + prefixName + "/" + SEQ, String.valueOf(0).getBytes());
           return result;
       } catch (Exception e) {
           throw new RuntimeException("create zookeeper node error", e);
       } finally {
           if (client != null)
               genericObjectPool.returnObject(client);
       }
   }
   public static void main(String[] args) {
       ExecutorService executorService = Executors.newFixedThreadPool(1);
       long startTime = System.currentTimeMillis();   //获取开始时间
       final ZKIncreaseSequenceHandler sequenceHandler = ZKIncreaseSequenceHandler.getInstance("192.168.0.65", "/sequence/p2p", "seq");
       int count = 10;
       final CountDownLatch cd = new CountDownLatch(count);
       for (int i = 0; i < count; i++) {
           executorService.execute(new Runnable() {
               public void run() {
                   System.out.printf("线程 %s %d \n", Thread.currentThread().getId(), sequenceHandler.nextId(SequenceEnum.ORDER));
                   cd.countDown();
               }
           });
       }
       try {
           cd.await();
       } catch (InterruptedException e) {
           LOGGER.error("Interrupted thread",e);
           Thread.currentThread().interrupt();
       }
       long endTime = System.currentTimeMillis(); //获取结束时间
      System.out.println("程序运行时间:" + (endTime - startTime) + "ms");
   }
   @Override
   public PooledObject makeObject() throws Exception {
       CuratorFramework client = CuratorFrameworkFactory.newClient(ZK_ADDRESS, new ExponentialBackoffRetry(1000, 3));
       client.start();
       return new DefaultPooledObject<>(client);
   }
   @Override
   public void destroyObject(PooledObject p) throws Exception {
   }
   @Override
   public boolean validateObject(PooledObject p) {
       return false;
   }
   @Override
   public void activateObject(PooledObject p) throws Exception {
   }
   @Override
   public void passivateObject(PooledObject p) throws Exception {
   }
}

测试结果如下,生成10000个id消耗=9443ms(两个线程耗时较长者),  平均每个id消耗0.9ms。

这还只是单zk连接的情况下,如果使用连接池来维护多个zk的连接,效率将成倍的提升。

四、结语

分布式ID生成器的实现有很多种。目前各方案也都各有特点。我们可以根据业务的具体要求,选择实现合适的方案。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/489457.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数据库的前世今生

译者 | 谭开朗编辑 | 屠敏来源 | CSDN&#xff08;ID&#xff1a;CSDNnews&#xff09;【CSDN 编者按】被称之为基础软件三驾马车之一的数据库&#xff0c;在经历了层次型和网状型、关系型数据型库以及更加强大的数据管理功能等三个时期之后&#xff0c;其在未来的发展历程中还…

android app links,Android APP Links 配置

一. 简介在Instant App开发中&#xff0c;需要app支持Instant App Links. 本文中&#xff0c;主要介绍其作用&#xff0c;配置必需条件&#xff0c;配置的过程。二. 作用App Links 本质上是 Deep Link 的一种延伸&#xff0c;我们知道&#xff0c;Deep Link配置在Manifest之后&…

【边缘计算】对边缘计算的理解与思考

来源&#xff1a;边缘计算社区在2019年第三届边缘计算技术研讨会上华为高级产业发展经理、ECC需求与总体组副主席黄还青发表了《ECC及华为在边缘计算领域的思考与实践》主题演讲&#xff0c;本文为黄还青演讲中对边缘计算理解及思考。首先我们认为边缘计算的兴起应该是在过去三…

【代码笔记】iOS-实现网络图片的异步加载和缓存

代码&#xff1a; - (void)viewDidLoad {[super viewDidLoad];// Do any additional setup after loading the view.self.title"EGOImageViewDemo"; //实现网络图片的异步加载和缓存 EGOImageView *imageView [[EGOImageView alloc] initWithPlaceholderImage:[UIIm…

python executemany执行延迟_运维架构师-Python 自动化运维开发-031

*运维架构师-Python 自动化运维开发-031十九、Python3 操作数据库1、Python3 操作 MySQL1、基本介绍Python3 操作 MySQL 数据库 可以使用的模块是 pymysql 和 MySQLdb。这个两个模块都是通过自己的 API 执行原生的 SQL 语句实现的。MySQLdb 是最早出现的一个操作 MySQL 数据库的…

bilibili 解析_用 Python 抓取 bilibili 弹幕并分析!

时隔一年&#xff0c;嵩哥带来他的新作《雨幕》。他依旧认真创作&#xff0c;追求高品质&#xff0c;作品在发表之前已听了五百遍以上。如此高品质的音乐&#xff0c;大家如何评价呢&#xff1f;通过哔哩哔哩上的视频弹幕&#xff0c;感受一下。01 实现思路首先&#xff0c;利用…

2019中国科学院、中国工程院院士增选名单正式发布

来源&#xff1a;医谷医学讯 今日&#xff0c;“两院”院士2019年增选名单正式公布。中国科学院选举产生了64名中国科学院院士和20名中国科学院外籍院士。中国工程院共选举产生75位院士和29位外籍院士。2019年新当选中国科学院院士名单&#xff08;共64人&#xff0c;分学部按姓…

微信 小程序 canvas

测试手机为IPHONE6,开发者工具版本0.10.102800。开发者工具0.11.112301版本也一样 微信小程序里的canvas 非 h5 canvas有很多不一样的地方&#xff0c;以下把微信小程序的canvas叫做wxcanvas 下面全是我一点点测试出的干货&#xff0c;耐心看&#xff1a; 1.wxcanvas,不像h5can…

Android App赞赏功能,微信公众号赞赏功能升级:作者可直接收到赞赏, iOS安卓均可用...

DoNews6月6日消息 (记者 费倩文)微信公众平台发布消息称&#xff0c;公众号赞赏功能升级为“喜欢作者”&#xff0c;开启了赞赏的文章在原创文章底部有“喜欢作者”的入口&#xff0c;作者可以直接收到读者赞赏&#xff0c;在 iOS 版和 Android 版微信上都可以使用。据了解&…

hashcode是什么意思_什么才是 Java 的基础知识?

作者&#xff1a;晓风轻链接&#xff1a;zhuanlan.zhihu.com/p/28615617近日里&#xff0c;很多人邀请我回答各种j2ee开发的初级问题&#xff0c;我无一都强调java初学者要先扎实自己的基础知识&#xff0c;那什么才是java的基础知识&#xff1f;又怎么样才算掌握了java的基础知…

BZOJ 1305 二分+网络流

思路&#xff1a; 建图我根本没有想到啊……. &#xff08;我是不会告诉你我借鉴了一下题解的思路&#xff09; 把每个人拆成喜欢的和不喜欢的点 男 喜欢 向 男 不喜欢 连 边权为k的边 如果男喜欢女 那么 男喜欢向 女喜欢 连 1 如果男 不喜欢女 那么 男不喜欢 向 女不喜欢 连1…

迄今最详细宇宙模型建成

来源&#xff1a;科技日报一个国际科研团队最近创建了迄今最详细的大尺度宇宙模型TNG50。这一虚拟宇宙“芳龄”约138亿岁、宽约2.3亿光年&#xff0c;包含数万个正处于演化中的星系&#xff0c;星系的细节程度与单星系模型中的相当。该模型跟踪了200多亿个代表暗物质、气体、恒…

线程操作函数

线程的挂起和恢复 DWORD SuspendThread ( HANDLE hThread ); //挂起线程 DWORD ResumeThread ( HANDLE hThread ); //恢复线程 SuspendThread 和 ResumeThread 都返回之前的挂起计数。 一个线程最多可以挂起MAXIMUM_SUSPEND_COUNT (WinNT.h中定义为127次)。 进程的挂起和恢…

先进激光三维成像雷达技术的研究进展与趋势分析

本文内容转载自《激光杂志》2019年第5期&#xff0c;版权归《激光杂志》编辑部所有。杨兴雨&#xff0c;李晨&#xff0c;郝丽婷&#xff0c;王元庆&#xff0c;古丽孜热∙艾尼外南京大学&#xff0c;伊犁师范学院摘要&#xff1a;激光雷达具有体积小、质量轻、探测距离远、高分…

Android Binder 系统学习笔记(一)Binder系统的基本使用方法

1.什么是RPC&#xff08;远程过程调用&#xff09; Binder系统的目的是实现远程过程调用&#xff08;RPC&#xff09;&#xff0c;即进程A去调用进程B的某个函数&#xff0c;它是在进程间通信&#xff08;IPC&#xff09;的基础上实现的。RPC的一个应用场景如下&#xff1a; A进…

mongodb 监听不到端口_干货|MongoDB简单操作和通过python进行操作

点击上方“AI遇见机器学习”&#xff0c;选择“星标”公众号重磅干货&#xff0c;第一时间送达这次我们主要来简单的讨论一下在MongoDB中如何更新数据(修改数据)&#xff0c;删除数据&#xff0c;以及如何通过Python调用MongoDB。一、简单使用MongoDB操作数据| a.更新数据| i.数…

人工智能+脑机接口:让我们距离“增强人类”越来越近

来源&#xff1a;资本实验室前段时间&#xff0c;一则新闻引发了广泛争议&#xff1a;国内一所小学利用头环来监控孩子的脑电波&#xff0c;以判断孩子上课是否走神。暂且不论该事件是否是一场打着高科技幌子的闹剧&#xff0c;头环本身所代表的脑机接口技术正在受到越来越多的…

哲学的未来

来源&#xff1a;哲学园作者&#xff1a;约翰R塞尔译者&#xff1a;GTY约翰塞尔生于1932年&#xff0c;当代著名哲学家&#xff0c;现为美国加州大学伯克利分校Slusser哲学教授&#xff0c;在语言哲学、心灵哲学和社会哲学领域贡献巨大&#xff0c;是目前在世的最著名的分析哲学…

怎么知道wx.config执行成功没_作为一个减肥40斤,且10年没反弹的普通人,这份瘦身经验分享给你...

“减肥”是女生老生常谈的话题&#xff0c;但是“减肥失败”、“越减越肥”也是很多女生面临的常态。所以做为成功减肥40斤且10多年没有反弹的人&#xff0c;我想来给大家一些自己的经验。很多姑娘知道减肥的关键因素是“热量差”&#xff0c;无论是增加运动&#xff0c;还是减…

html代码type,HTML中type是什么意思

在HTML中&#xff0c;type是类型的意思&#xff0c;是一个标签属性&#xff0c;主要用于定义标签元素的类型或文档(脚本)的MIME类型&#xff1b;例在input标签中type属性可以规定input元素的类型&#xff0c;在script标签中type属性可以规定脚本的MIME类型。推荐&#xff1a;ht…