本文已收录到1.1K Star数开源学习指南——《大厂面试指北》,如果想要了解更多大厂面试相关的内容及获取《大厂面试指北》离线PDF版,请扫描下方二维码码关注公众号“大厂面试”,谢谢大家了!

《大厂面试指北》最佳阅读地址:

http://notfound9.github.io/interviewGuide/

《大厂面试指北》项目地址:

https://github.com/NotFound9/interviewGuide

获取《大厂面试指北》离线PDF版,请扫描下方二维码关注公众号“大厂面试”

《大厂面试指北》项目截图:

摘要

本文主要是对美团的分布式ID框架Leaf的原理进行介绍,针对Leaf原项目中的一些issue,对Leaf项目增加一些功能支持,问题修复及优化改进,改进后的项目地址在这里:

Leaf项目改进计划 https://github.com/NotFound9/Leaf

Leaf原理分析

Snowflake生成ID的模式

7849276-4d1955394baa3c6d.png



snowflake算法对于ID的位数是上图这样分配的:

1位的符号位+41位时间戳+10位workID+12位序列号

加起来一共是64个二进制位,正好与Java中的long类型的位数一样。

美团的Leaf框架对于snowflake算法进行了一些位数调整,位数分配是这样:

最大41位时间差+10位的workID+12位序列化

虽然看美团对Leaf的介绍文章里面说

Leaf-snowflake方案完全沿用snowflake方案的bit位设计,即是“1+41+10+12”的方式组装ID号。

其实看代码里面是没有专门设置符号位的,如果timestamp过大,导致时间差占用42个二进制位,时间差的第一位为1时,可能生成的id转换为十进制后会是负数:

  1. //timestampLeftShift是22,workerIdShift是12
  2. long id = ((timestamp - twepoch) << timestampLeftShift) | (workerId << workerIdShift) | sequence;

时间差是什么?

因为时间戳是以1970年01月01日00时00分00秒作为起始点,其实我们一般取的时间戳其实是起始点到现在的时间差,如果我们能确定我们取的时间都是某个时间点以后的时间,那么可以将时间戳的起始点改成这个时间点,Leaf项目中,如果不设置起始时间,默认是2010年11月4日09:42:54,这样可以使得支持的最大时间增长,Leaf框架的支持最大时间是起始点之后的69年。

workID怎么分配?

Leaf使用Zookeeper作为注册中心,每次机器启动时去Zookeeper特定路径/forever/下读取子节点列表,每个子节点存储了IP:Port及对应的workId,遍历子节点列表,如果存在当前IP:Port对应的workId,就使用节点信息中存储的workId,不存在就创建一个永久有序节点,将序号作为workId,并且将workId信息写入本地缓存文件workerID.properties,供启动时连接Zookeeper失败,读取使用。因为workId只分配了10个二进制位,所以取值范围是0-1023。

序列号怎么生成?

序列号是12个二进制位,取值范围是0到4095,主要保证同一个leaf服务在同一毫秒内,生成的ID的唯一性。

序列号是生成流程如下:

1.当前时间戳与上一个ID的时间戳在同一毫秒内,那么对sequence+1,如果sequence+1超过了4095,那么进行等待,等到下一毫秒到了之后再生成ID。

2.当前时间戳与上一个ID的时间戳不在同一毫秒内,取一个100以内的随机数作为序列号。

  1. if (lastTimestamp == timestamp) {
  2. sequence = (sequence + 1) & sequenceMask;
  3. if (sequence == 0) {
  4. //seq 为0的时候表示是下一毫秒时间开始对seq做随机
  5. sequence = RANDOM.nextInt(100);
  6. timestamp = tilNextMillis(lastTimestamp);
  7. }
  8. } else {
  9. //如果是新的ms开始
  10. sequence = RANDOM.nextInt(100);
  11. }
  12. lastTimestamp = timestamp;

segment生成ID的模式

5e4ff128.png



这种模式需要依赖MySQL,表字段biz_tag代表业务名,max_id代表该业务目前已分配的最大ID值,step代表每次Leaf往数据库请求时,一次性分配的ID数量。

大致流程就是每个Leaf服务在内存中有两个Segment实例,每个Segement保存一个分段的ID,

一个Segment是当前用于分配ID,有一个value属性保存这个分段已分配的最大ID,以及一个max属性这个分段最大的ID。

另外一个Segement是备用的,当一个Segement用完时,会进行切换,使用另一个Segement进行使用。

当一个Segement的分段ID使用率达到10%时,就会触发另一个Segement去DB获取分段ID,初始化好分段ID供之后使用。

  1. Segment {
  2. private AtomicLong value = new AtomicLong(0);
  3. private volatile long max;
  4. private volatile int step;
  5. }
  6. SegmentBuffer {
  7. private String key;
  8. private Segment[] segments; //双buffer
  9. private volatile int currentPos; //当前的使用的segment的index
  10. private volatile boolean nextReady; //下一个segment是否处于可切换状态
  11. private volatile boolean initOk; //是否初始化完成
  12. private final AtomicBoolean threadRunning; //线程是否在运行中
  13. private final ReadWriteLock lock;
  14. private volatile int step;
  15. private volatile int minStep;
  16. private volatile long updateTimestamp;
  17. }

Leaf项目改进

目前Leaf项目存在的问题是

Snowflake生成ID相关:

1.注册中心只支持Zookeeper

而对于一些小公司或者项目组,其他业务没有使用到Zookeeper的话,为了部署Leaf服务而维护一个Zookeeper集群的代价太大。所以原项目中有issue在问”怎么支持非Zookeeper的注册中心“,由于一般项目中使用MySQL的概率会大很多,所以增加了使用MySQL作为注册中心,本地配置作为注册中心的功能。

2.潜在的时钟回拨问题

由于启动前,服务器时间调到了以前的时间或者进行了回拨,连接Zookeeper失败时会使用本地缓存文件workerID.properties中的workerId,而没有校验该ID生成的最大时间戳,可能会造成ID重复,对这个问题进行了修复。

3.时间差过大时,生成id为负数

因为缺少对时间差的校验,当时间差过大,转换为二进制数后超过41位后,在生成ID时会造成溢出,使得符号位为1,生成id为负数。

Segement生成ID相关:

没有太多问题,主要是根据一些issue对代码进行了性能优化。

具体改进如下:

Snowflake生成ID相关的改进:

1.针对Leaf原项目中的issue#84,增加zk_recycle模式(注册中心为zk,workId循环使用)

2.针对Leaf原项目中的issue#100,增加MySQL模式(注册中心为MySQL)

3.针对Leaf原项目中的issue#100,增加Local模式(注册中心为本地项目配置)

4.针对Leaf原项目中的issue#84,修复启动时时钟回拨的问题

5.针对Leaf原项目中的issue#106,修复时间差过大,超过41位溢出,导致生成的id负数的问题

Segement生成ID相关的改进:

1.针对Leaf原项目中的issue#68,优化SegmentIDGenImpl.updateCacheFromDb()方法。

2.针对Leaf原项目中的 issue#88,使用位运算&替换取模运算

snowflake算法生成ID的相关改进

Leaf项目原来的注册中心的模式(我们暂时命令为zk_normal模式)

使用Zookeeper作为注册中心,每次机器启动时去Zookeeper特定路径下读取子节点列表,如果存在当前IP:Port对应的workId,就使用节点信息中存储的workId,不存在就创建一个永久有序节点,将序号作为workId,并且将workId信息写入本地缓存文件workerID.properties,供启动时连接Zookeeper失败,读取使用。

1.针对Leaf原项目中的issue#84,增加zk_recycle模式(注册中心为zk,workId循环使用)

问题详情:

issue#84:workid是否支持回收?

SnowflakeService模式中,workid是否支持回收?分布式环境下,每次重新部署可能就换了一个ip,如果没有回收的话1024个机器标识很快就会消耗完,为什么zk不用临时节点去存储呢,这样能动态感知服务上下线,对workid进行管理回收?

解决方案:

开发了zk_recycle模式,针对使用snowflake生成分布式ID的技术方案,原本是使用Zookeeper作为注册中心为每个服务根据IP:Port分配一个固定的workId,workId生成范围为0到1023,workId不支持回收,所以在Leaf的原项目中有人提出了一个issue#84 workid是否支持回收?,因为当部署Leaf的服务的IP和Port不固定时,如果workId不支持回收,当workId超过最大值时,会导致生成的分布式ID的重复。所以增加了workId循环使用的模式zk_recycle。

如何使用zk_recycle模式?

在Leaf/leaf-server/src/main/resources/leaf.properties中添加以下配置

  1. //开启snowflake服务
  2. leaf.snowflake.enable=true
  3. //leaf服务的端口,用于生成workId
  4. leaf.snowflake.port=
  5. //将snowflake模式设置为zk_recycle,此时注册中心为Zookeeper,并且workerId可复用
  6. leaf.snowflake.mode=zk_recycle
  7. //zookeeper的地址
  8. leaf.snowflake.zk.address=localhost:2181

启动LeafServerApplication,调用/api/snowflake/get/test就可以获得此种模式下生成的分布式ID。

  1. curl domain/api/snowflake/get/test
  2. 1256557484213448722

zk_recycle模式实现原理

按照上面的配置在leaf.properties里面进行配置后,

  1. if(mode.equals(SnowflakeMode.ZK_RECYCLE)) {//注册中心为zk,对ip:port分配的workId是课循环利用的模式
  2. String zkAddress = properties.getProperty(Constants.LEAF_SNOWFLAKE_ZK_ADDRESS);
  3. RecyclableZookeeperHolder holder = new RecyclableZookeeperHolder(Utils.getIp(),port,zkAddress);
  4. idGen = new SnowflakeIDGenImpl(holder);
  5. if (idGen.init()) {
  6. logger.info("Snowflake Service Init Successfully in mode " + mode);
  7. } else {
  8. throw new InitException("Snowflake Service Init Fail");
  9. }
  10. }

此时SnowflakeIDGenImpl使用的holder是RecyclableZookeeperHolder的实例,workId是可循环利用的,RecyclableZookeeperHolder工作流程如下:

1.首先会在未使用的workId池(zookeeper路径为/snowflake/leaf.name/recycle/notuse/)中生成所有workId。

2.然后每次服务器启动时都是去未使用的workId池取一个新的workId,然后放到正在使用的workId池(zookeeper路径为/snowflake/leaf.name/recycle/inuse/)下,将此workId用于Id生成,并且定时上报时间戳,更新zookeeper中的节点信息。

3.并且定时检测正在使用的workId池,发现某个workId超过最大时间没有更新时间戳的workId,会把它从正在使用的workId池移出,然后放到未使用的workId池中,以供workId循环使用。

4.并且正在使用这个很长时间没有更新时间戳的workId的服务器,在发现自己超过最大时间,还没有上报时间戳成功后,会停止id生成服务,以防workId被其他服务器循环使用,导致id重复。

2.针对Leaf原项目中的issue#100,增加MySQL模式(注册中心为MySQL)

问题详情:

issue#100:如何使用非zk的注册中心?

解决方案:

开发了mysql模式,这种模式注册中心为MySQL,针对每个ip:port的workid是固定的。

如何使用这种mysql模式?

需要先在数据库执行项目中的leaf_workerid_alloc.sql,完成建表,然后在Leaf/leaf-server/src/main/resources/leaf.properties中添加以下配置

  1. //开启snowflake服务
  2. leaf.snowflake.enable=true
  3. //leaf服务的端口,用于生成workId
  4. leaf.snowflake.port=
  5. //将snowflake模式设置为mysql,此时注册中心为Zookeeper,workerId为固定分配
  6. leaf.snowflake.mode=mysql
  7. //mysql数据库地址
  8. leaf.jdbc.url=
  9. leaf.jdbc.username=
  10. leaf.jdbc.password=

启动LeafServerApplication,调用/api/snowflake/get/test就可以获得此种模式下生成的分布式ID。

  1. curl domain/api/snowflake/get/test
  2. 1256557484213448722

实现原理

使用上面的配置后,此时SnowflakeIDGenImpl使用的holder是SnowflakeMySQLHolder的实例。实现原理与Leaf原项目默认的模式,使用Zookeeper作为注册中心,每个ip:port的workid是固定的实现原理类似,只是注册,获取workid,及更新时间戳是与MySQL进行交互,而不是Zookeeper。

  1. if (mode.equals(SnowflakeMode.MYSQL)) {//注册中心为mysql
  2. DruidDataSource dataSource = new DruidDataSource();
  3. dataSource.setUrl(properties.getProperty(Constants.LEAF_JDBC_URL));
  4. dataSource.setUsername(properties.getProperty(Constants.LEAF_JDBC_USERNAME));
  5. dataSource.setPassword(properties.getProperty(Constants.LEAF_JDBC_PASSWORD));
  6. dataSource.init();
  7. // Config Dao
  8. WorkerIdAllocDao dao = new WorkerIdAllocDaoImpl(dataSource);
  9. SnowflakeMySQLHolder holder = new SnowflakeMySQLHolder(Utils.getIp(), port, dao);
  10. idGen = new SnowflakeIDGenImpl(holder);
  11. if (idGen.init()) {
  12. logger.info("Snowflake Service Init Successfully in mode " + mode);
  13. } else {
  14. throw new InitException("Snowflake Service Init Fail");
  15. }
  16. }

3.针对Leaf原项目中的issue#100,增加Local模式(注册中心为本地项目配置)

问题详情:

issue#100:如何使用非zk的注册中心?

解决方案:

开发了local模式,这种模式就是适用于部署Leaf服务的IP和Port基本不会变化的情况,就是在Leaf项目中的配置文件leaf.properties中显式得配置某某IP:某某Port对应哪个workId,每次部署新机器时,将IP:Port的时候在项目中添加这个配置,然后启动时项目会去读取leaf.properties中的配置,读取完写入本地缓存文件workId.json,下次启动时直接读取workId.json,最大时间戳也每次同步到机器上的缓存文件workId.json中。

如何使用这种local模式?

在Leaf/leaf-server/src/main/resources/leaf.properties中添加以下配置

  1. //开启snowflake服务
  2. leaf.snowflake.enable=true
  3. //leaf服务的端口,用于生成workId
  4. leaf.snowflake.port=
  5. #注册中心为local的的模式
  6. #leaf.snowflake.mode=local
  7. #leaf.snowflake.local.workIdMap=
  8. #workIdMap的格式是这样的{"Leaf服务的ip:端口":"固定的workId"},例如:{"10.1.46.33:8080":1,"10.1.46.33:8081":2}

启动LeafServerApplication,调用/api/snowflake/get/test就可以获得此种模式下生成的分布式ID。

  1. curl domain/api/snowflake/get/test
  2. 1256557484213448722

4.针对Leaf原项目中的issue#84,修复启动时时钟回拨的问题

问题详情:

issue#84:因为当使用默认的模式(我们暂时命令为zk_normal模式),注册中心为Zookeeper,workId不可复用,上面介绍了这种模式的工作流程,当Leaf服务启动时,连接Zookeeper失败,那么会去本机缓存中读取workerID.properties文件,读取workId进行使用,但是由于workerID.properties中只存了workId信息,没有存储上次上报的最大时间戳,所以没有进行时间戳判断,所以如果机器的当前时间被修改到之前,就可能会导致生成的ID重复。

解决方案:

所以增加了更新时间戳到本地缓存的机制,每次在上报时间戳时将时间戳同时写入本机缓存workerID.properties,并且当使用本地缓存workerID.properties中的workId时,对时间戳进行校验,当前系统时间戳<缓存中的时间戳时,才使用这个workerId。

  1. //连接失败,使用本地workerID.properties中的workerID,并且对时间戳进行校验。
  2. try {
  3. Properties properties = new Properties();
  4. properties.load(new FileInputStream(new File(PROP_PATH.replace("{port}", port + ""))));
  5. Long maxTimestamp = Long.valueOf(properties.getProperty("maxTimestamp"));
  6. if (maxTimestamp!=null && System.currentTimeMillis() <maxTimestamp) {
  7. throw new CheckLastTimeException("init timestamp check error,forever node timestamp gt this node time");
  8. }
  9. workerID = Integer.valueOf(properties.getProperty("workerID"));
  10. LOGGER.warn("START FAILED ,use local node file properties workerID-{}", workerID);
  11. } catch (Exception e1) {
  12. LOGGER.error("Read file error ", e1);
  13. return false;
  14. }
  15. //定时任务每3s执行一次updateNewData()方法,调用更新updateLocalWorkerID()更新缓存文件workerID.properties
  16. void updateNewData(CuratorFramework curator, String path) {
  17. try {
  18. if (System.currentTimeMillis() < lastUpdateTime) {
  19. return;
  20. }
  21. curator.setData().forPath(path, buildData().getBytes());
  22. updateLocalWorkerID(workerID);
  23. lastUpdateTime = System.currentTimeMillis();
  24. } catch (Exception e) {
  25. LOGGER.info("update init data error path is {} error is {}", path, e);
  26. }
  27. }

5.针对Leaf原项目中的issue#106,修复时间差过大,超过41位溢出,导致生成的id负数的问题

问题详情:

因为Leaf框架是沿用snowflake的位数分配

最大41位时间差+10位的workID+12位序列化,但是由于snowflake是强制要求第一位为符号位0,否则生成的id转换为十进制后会是复试,但是Leaf项目中没有对时间差进行校验,当时间戳过大或者自定义的twepoch设置不当过小,会导致计算得到的时间差过大,转化为2进制后超过41位,且第一位为1,会导致生成的long类型的id为负数,例如当timestamp = twepoch+2199023255552L时,

此时在生成id时,timestamp - twepoch会等于2199023255552,2199023255552转换为二进制后是1+41个0,此时生成的id由于符号位是1,id会是负数-9223372036854775793

  1. long id = ((timestamp - twepoch) << timestampLeftShift) | (workerId << workerIdShift) | sequence;

解决方案:

  1. //一开始将最大的maxTimeStamp计算好
  2. this.maxTimeStamp = ~(-1L << timeStampBits) + twepoch;
  3. //然后生成ID时进行校验
  4. if (timestamp>maxTimeStamp) {
  5. throw new OverMaxTimeStampException("current timestamp is over maxTimeStamp, the generate id will be negative");
  6. }

针对Segement生成分布式ID相关的改进

1.针对Leaf原项目中的issue#68,优化SegmentIDGenImpl.updateCacheFromDb()方法

针对issue#68里面的优化方案,对Segement Buffer的缓存数据与DB数据同步的工作流程进行了进一步优化,主要是对

对SegmentIDGenImpl.updateCacheFromDb()方法进行了优化。

原方案工作流程:

1.遍历cacheTags,将dbTags的副本insertTagsSet中存在的元素移除,使得insertTagsSet只有db新增的tag

2.遍历insertTagsSet,将这些新增的元素添加到cache中

3.遍历dbTags,将cacheTags的副本removeTagsSet中存在的元素移除,使得removeTagsSet只有cache中过期的tag

4.遍历removeTagsSet,将过期的元素移除cache

这种方案需要经历四次循环,使用两个HashSet分别存储db中新增的tag,cache中过期的tag,

并且为了筛选出新增的tag,过期的tag,对每个现在使用的tag有两次删除操作,

原有方案代码如下:

  1. List<String> dbTags = dao.getAllTags();
  2. if (dbTags == null || dbTags.isEmpty()) {
  3. return;
  4. }
  5. List<String> cacheTags = new ArrayList<String>(cache.keySet());
  6. Set<String> insertTagsSet = new HashSet<>(dbTags);
  7. Set<String> removeTagsSet = new HashSet<>(cacheTags);
  8. //db中新加的tags灌进cache
  9. for(int i = 0; i < cacheTags.size(); i++){
  10. String tmp = cacheTags.get(i);
  11. if(insertTagsSet.contains(tmp)){
  12. insertTagsSet.remove(tmp);
  13. }
  14. }
  15. for (String tag : insertTagsSet) {
  16. SegmentBuffer buffer = new SegmentBuffer();
  17. buffer.setKey(tag);
  18. Segment segment = buffer.getCurrent();
  19. segment.setValue(new AtomicLong(0));
  20. segment.setMax(0);
  21. segment.setStep(0);
  22. cache.put(tag, buffer);
  23. logger.info("Add tag {} from db to IdCache, SegmentBuffer {}", tag, buffer);
  24. }
  25. //cache中已失效的tags从cache删除
  26. for(int i = 0; i < dbTags.size(); i++){
  27. String tmp = dbTags.get(i);
  28. if(removeTagsSet.contains(tmp)){
  29. removeTagsSet.remove(tmp);
  30. }
  31. }
  32. for (String tag : removeTagsSet) {
  33. cache.remove(tag);
  34. logger.info("Remove tag {} from IdCache", tag);
  35. }

实际上我们并不需要这些中间过程,现方案工作流程:

只需要遍历dbTags,判断cache中是否存在这个key,不存在就是新增元素,进行新增。

遍历cacheTags,判断dbSet中是否存在这个key,不存在就是过期元素,进行删除。

现有方案代码:

  1. List<String> dbTags = dao.getAllTags();
  2. if (dbTags == null || dbTags.isEmpty()) {
  3. return;
  4. }
  5. //将dbTags中新加的tag添加cache,通过遍历dbTags,判断是否在cache中存在,不存在就添加到cache
  6. for (String dbTag : dbTags) {
  7. if (cache.containsKey(dbTag)==false) {
  8. SegmentBuffer buffer = new SegmentBuffer();
  9. buffer.setKey(dbTag);
  10. Segment segment = buffer.getCurrent();
  11. segment.setValue(new AtomicLong(0));
  12. segment.setMax(0);
  13. segment.setStep(0);
  14. cache.put(dbTag, buffer);
  15. logger.info("Add tag {} from db to IdCache, SegmentBuffer {}", dbTag, buffer);
  16. }
  17. }
  18. List<String> cacheTags = new ArrayList<String>(cache.keySet());
  19. Set<String> dbTagSet = new HashSet<>(dbTags);
  20. //将cache中已失效的tag从cache删除,通过遍历cacheTags,判断是否在dbTagSet中存在,不存在说明过期,直接删除
  21. for (String cacheTag : cacheTags) {
  22. if (dbTagSet.contains(cacheTag) == false) {
  23. cache.remove(cacheTag);
  24. logger.info("Remove tag {} from IdCache", cacheTag);
  25. }
  26. }

两个方案对比:

  • 空间复杂度

    相比原方案需要使用两个HashSet,这种方案的只需要使用一个hashSet,空间复杂度会低一些。
  • 时间复杂度

    总遍历次数会比原来的少,时间复杂度更低,因为判断是新增,过期的情况就直接处理了,不需要后续再单独遍历,

    而且不需要对cache和dbtag的交集进行删除操作,因为原来方案为了获得新增的元素,是将dbSet的副本中现有元素进行删除得到。
  • 代码可读性

    原方案是4个for循环,总共35行代码,现方案是2个for循环,总共25行代码,更加简洁易懂。

2.针对Leaf原项目中的issue#88,使用位运算&替换取模运算

这个更新是针对这个issue#88 提出的问题,使用位运算&来代替取模运算%,执行效率更高。

原代码:

  1. public int nextPos() {
  2. return (currentPos + 1) % 2;
  3. }

现代码:

  1. public int nextPos() {
  2. return (currentPos + 1) & 1;
  3. }

美团分布式ID生成框架Leaf源码分析及优化改进的更多相关文章

  1. Leaf:美团分布式ID生成服务开源

    Leaf是美团基础研发平台推出的一个分布式ID生成服务,名字取自德国哲学家.数学家莱布尼茨的一句话:“There are no two identical leaves in the world.”L ...

  2. 框架-spring源码分析(一)

    框架-spring源码分析(一) 参考: https://www.cnblogs.com/heavenyes/p/3933642.html http://www.cnblogs.com/BINGJJF ...

  3. 框架-springmvc源码分析(二)

    框架-springmvc源码分析(二) 参考: http://www.cnblogs.com/leftthen/p/5207787.html http://www.cnblogs.com/leftth ...

  4. 框架-springmvc源码分析(一)

    框架-springmvc源码分析(一) 参考: http://www.cnblogs.com/heavenyes/p/3905844.html#a1 https://www.cnblogs.com/B ...

  5. 高性能网络I/O框架-netmap源码分析

    from:http://blog.chinaunix.net/uid-23629988-id-3594118.html 博主这篇文章写的很好 感觉很有借签意义 值得阅读 高性能网络I/O框架-netm ...

  6. Java8集合框架——LinkedList源码分析

    java.util.LinkedList 本文的主要目录结构: 一.LinkedList的特点及与ArrayList的比较 二.LinkedList的内部实现 三.LinkedList添加元素 四.L ...

  7. 基于vue实现一个简单的MVVM框架(源码分析)

    不知不觉接触前端的时间已经过去半年了,越来越发觉对知识的学习不应该只停留在会用的层面,这在我学jQuery的一段时间后便有这样的体会. 虽然jQuery只是一个JS的代码库,只要会一些JS的基本操作学 ...

  8. Django rest framework框架——APIview源码分析

    一.什么是rest REST其实是一种组织Web服务的架构,而并不是我们想象的那样是实现Web服务的一种新的技术,更没有要求一定要使用HTTP.其目标是为了创建具有良好扩展性的分布式系统. 可用一句话 ...

  9. spark源码分析以及优化

    第一章.spark源码分析之RDD四种依赖关系 一.RDD四种依赖关系 RDD四种依赖关系,分别是 ShuffleDependency.PrunDependency.RangeDependency和O ...

随机推荐

  1. asp.net mvc 接收jquery ajax发送的数组对象

    <script type="text/javascript"> $(function () { var obj = { name: "军需品", m ...

  2. Redisson 实现分布式锁的原理分析

    写在前面 在了解分布式锁具体实现方案之前,我们应该先思考一下使用分布式锁必须要考虑的一些问题.​ 互斥性:在任意时刻,只能有一个进程持有锁. 防死锁:即使有一个进程在持有锁的期间崩溃而未能主动释放锁, ...

  3. web测试流程

    1.立项后测试需要拿到文档(需求说明书,原型图,接口文档,) 2.需求评审 3.用例编写(主流程,备流程,异常流,业务规则,正常类,异常类,页面检查) 测试用例编写方法(等价类划分,边界值分析法,错误 ...

  4. C# WCF之用接口创建服务契约、部署及客户端连接

    服务契约描述了暴露给外部的类型(接口或类).服务所支持的操作.使用的消息交换模式和消息的格式.每个WCF服务必须实现至少一个服务契约.使用服务契约必须要引用命名空间System.ServiceMode ...

  5. Scapy的基本使用

    关于Scapy Scapy是一个可以让用户发送.侦听和解析并伪装网络报文的Python程序.这些功能可以用于制作侦测.扫描和攻击网络的工具. 换言之,Scapy 是一个强大的操纵报文的交互程序.它可以 ...

  6. serialize和json_encode 区别

    (1)serialize主要用于php的序列化,存储到文件或者数据库中,json_encode 也是序列化,但是 主要用于与其他语言比如js进行交互使用,对于传输来说,json有许多优点. (2)在显 ...

  7. ajax后台返回指定的错误码

    js: $.ajax({ type: "POST", url: 'post.php', data: serialNumber + "&getSerialNumbe ...

  8. discuz修改禁止性别保密选项

    第一步找到source/function/function_profile.php 第二步  注释下面的代码 else { $html .= '<option value="0&quo ...

  9. 20199308《Linux内核原理与分析》第十一周作业

    缓冲区溢出漏洞实验 实验步骤 一.初始设置 1.Ubuntu 和其他一些 Linux 系统中,使用地址空间随机化来随机堆(heap)和栈(stack)的初始地址,这使得猜测准确的内存地址变得十分困难, ...

  10. dns的抓包分析

    dns: 域名系统(服务)协议 dns的解析全过程: 1. 浏览器先检查自身缓存中有没有被解析过的这个域名对应的ip地址,如果有,解析结束.同时域名被缓存的时间也可通过TTL属性来设置. 2. 如果浏 ...