多图详解:不停机分库分表五个步骤


1 理论知识

1.1 分库分表是否必要

分库分表确实可以解决单表数据量大这个问题,但是并非首选。因为分库分表至少引入了三个必须解决的突出问题。

第一是分库分表方案本身具有的复杂性。第二是本地事务失效问题,原本在同一个数据库中可以保证强一致性业务逻辑,分库之后事务失效。第三是难以聚合查询问题,因为分库分表后查询条件中必须带有shardingKey,所以限制了很多查询场景。

我们在之前文章《面试官问单表数据量大是否必须分库分表》介绍过解决单表数据量过大问题,可以按照删、换、分、拆、异、热这六个字顺序进行处理,而不是一上来就分库分表。

删是指删除历史数据并进行归档。换是指不要只使用数据库资源,有些数据可以存储至其它替代资源。分是指读写分离,增加多个读实例应对读多写少的互联网场景。拆是指分库分表,将数据分散至不同的库表中减轻压力。异指数据异构,将一份数据根据不同业务需求保存多份。热是指热点数据,这是一个非常值得注意的问题。


1.2 分库分表两大维度

假设有一个电商数据库存放订单、商品、支付三张业务表。随着业务量越来越大,这三张业务数据表也越来越大,查询性能显著降低,数据拆分势在必行,那么数据拆分可以从纵向和横向两个维度进行。


1.2.1 纵向拆分

纵向拆分就是按照业务拆分,我们将电商数据库拆分成三个库,订单库、商品库。支付库,订单表在订单库,商品表在商品库,支付表在支付库。这样每个库只需要存储本业务数据,物理隔离不会互相影响。


02 纵向分表.jpg


1.2.2 横向拆分

按照纵向拆分方案,现在我们已经有三个库了,平稳运行了一段时间。但是随着业务增长,每个单库单表的数据量也越来越大,逐渐到达瓶颈。

这时我们就要对数据表进行横向拆分,所谓横向拆分就是根据某种规则将单库单表数据分散到多库多表,从而减小单库单表的压力。

横向拆分策略有很多方案,最重要的一点是选好ShardingKey,也就是按照哪一列进行拆分,怎么分取决于我们访问数据的方式。


(1) 范围分片

如果我们选择的ShardingKey是订单创建时间,那么分片策略是拆分四个数据库,分别存储每季度数据,每个库包含三张表,分别存储每个月数据:


03 横向分表_范围分表.jpg


这个方案的优点是对范围查询比较友好,例如我们需要统计第一季度的相关数据,查询条件直接输入时间范围即可。这个方案的问题是容易产生热点数据。例如双11当天下单量特别大,就会导致11月这张表数据量特别大从而造成访问压力。


(2) 查表分片

查表法是根据一张路由表决定ShardingKey路由到哪一张表,每次路由时首先到路由表里查到分片信息,再到这个分片去取数据。我们分析一个查表法思想应用实际案例。

Redis官方在3.0版本之后提供了集群方案RedisCluster,其中引入了哈希槽(slot)这个概念。一个集群固定有16384个槽,在集群初始化时这些槽会平均分配到Redis集群节点上。每个key请求最终落到哪个槽计算公式是固定的:

SLOT = CRC16(key) mod 16384

一个key请求过来怎么知道去哪台Redis节点获取数据?这就要用到查表法思想:

(1) 客户端连接任意一台Redis节点,假设随机访问到节点A
(2) 节点A根据key计算出slot值
(3) 每个节点都维护着slot和节点映射关系表
(4) 如果节点A查表发现该slot在本节点,直接返回数据给客户端
(5) 如果节点A查表发现该slot不在本节点,返回给客户端一个重定向命令,告诉客户端应该去哪个节点请求这个key的数据
(6) 客户端向正确节点发起连接请求

查表法方案优点是可以灵活制定路由策略,如果我们发现有的分片已经成为热点则修改路由策略。缺点是多一次查询路由表操作增加耗时,而且路由表如果是单点也可能会有单点问题。


(3) 哈希分片

现在比较流行的分片方法是哈希分片,相较于范围分片,哈希分片可以较为均匀将数据分散在数据库中。我们现在将订单库拆分为4个库编号为[0,3],每个库包含3张表编号为[0,2],如下图如所示:


04 横向分表_哈希分表_1.jpg


我们选择使用orderId作为ShardingKey,那么orderId=100这个订单会保存在哪张表?因为是分库分表,第一步确定路由到哪一个库,取模计算结果表示库表序号:

db_index = 100 % 4 = 0

第二步确定路由到哪一张表:

table_index = 100 % 3 = 1

第三步数据路由到0号库1号表:


04 横向分表_哈希分表_2.jpg


在实际开发中路由逻辑并不需要我们手动实现,因为有许多开源框架通过配置就可以实现路由功能,例如ShardingSphere、TDDL框架等等。


2 分库分表准备工作

2.1 计算库表数量

分几个库和几张表是在分库分表工作开始前必须要回答的问题,我们首先看看阿里巴巴开发手册的建议:单表行数超过500万行或者单表容量超过2GB才推荐进行分库分表,如果预计3年后数据量根本达不到这个级别,请不要在创建表时就分库分表。

我们提取出这个建议的两个关键词500万、3年作为预估库表数的基线,假设业务数据日增量60万,那么应该如何预估需要分多少个库,多少张表呢?

日增量60万计算3年后数据总量:

三年数据总量 = 60 * 365 * 3 = 65700

随着后续业务发展日增量会超过60万,所以我们要对数据总量进行冗余,冗余指数是多少根据业务情况而定,本文按照3倍冗余:

三年数据总量三倍冗余 = 65700 * 3 = 197100

按照单表500万并向上取整至2的幂次计算表数量

表数量 = 197100 / 500 = 394.2 向上取整 = 512

所有表放在一个库并不合适,因为随着数据量增大,访问并发量也会呈正相关增大,一个数据库实例是难以支撑的。本文按照一个数据库实例包含32张表计算库数量:

库数量 = 512 / 32 = 16

2.2 shardingKey

确定shardingKey非常关键,因为作为分片指标,当数据拆分至多个库表之后,代理层只能根据shardingKey进行表路由。假设我们设置了userId作为shardingKey,那么后续DML操作都必须包含userId字段。但是现在有一种场景只有orderId作为查询条件,那么我们应该如何处理这种场景呢?

第一种方案是设计orderId包含userId相关特征,这样即使只有订单号作为查询条件,也可以截取userId特征进行分片:

订单号 = 毫秒数 + 版本号 + userId后六位 + 全局序列号

第二种方案是数据异构,核心思想是以空间换时间,一份数据根据不同维度存储到多个数据介质,数据异构一般分为如下类型。

数据异构至MySQL:我们可以选择orderId作为shardingKey存储至另一个数据库实例,那么orderId就可以作为条件进行查询。

数据异构至ES:如果每一个维度都新建一个数据库实例也是不现实的,所以我们可以将数据同步至ES满足多维度查询需求。

数据异构至Hive:MySQL和ES可以满足实时查询需求,Hive可以满足离线分析需求,报表等数据分析工作无需通过主库,而是可以通过Hive进行。

现在又引出一个新问题,业务不可能每次都将数据写入多个数据源,这样会带来性能问题和数据一致性问题,所以需要一个管道进行各数据源之间同步,阿里开源的canal组件可以解决这个问题。


3 分库分表实例

在完成准备工作之后,我们可以开始分库分表工作了。分库分表方法有很多种,但是说到底都是在处理两类数据:存量和增量。存量表示旧数据库已经存在的数据,增量表示不存在于旧数据库待新增或者变更的数据。根据存量和增量这两种类型,我们可以将分库分表方法分为停服拆分和不停服拆分。


3.1 停服拆分

停服是指停止服务,系统不再接收新业务数据,那么旧数据在分库分表这个时间段内是静止不变的,数据全部变为了存量数据。停服拆分一般分为三个阶段。

第一阶段首先编写代理层和新DAO,代理层通过开关决定访问旧表还是新表,此时流量还是全部访问旧表:


00 停服_阶段1.jpg


第二阶段停止服务,整个应用都没有流量,旧表数据已经处于静止状态,此时通过脚本将存量数据从旧表迁移至新表:


00 停服_阶段2.jpg


第三阶段通过代理层访问新表,如果出现错误可以停服排查问题:


00 停服_阶段3.jpg


3.2 不停服拆分

停服拆分方案比较简单,但是在分表这段时间没有业务流量,对业务是有损的,所以我们一般采用不停服拆分方案,一边有流量访问,一边进行分库分表,此时数据不仅有存量还有增量,相对而言会复杂一些。

第一阶段首先编写代理层和新DAO,代理层通过开关决定访问旧表还是新表,此时流量还是全部访问旧表:


01 不停服_阶段1.jpg


第二阶段开启双写,增量数据不仅在旧表新增和修改,也在新表新增和修改,日志或者临时表记录下写入新表ID起始值,旧表中小于这个值的数据就是存量数据:


02 不停服_阶段2.jpg


第三阶段存量数据迁移,通过脚本将存量数据写入新表:


03 不停服_阶段3.jpg


第四阶段停读旧表改读新表,此时新表已经承载了所有读写业务,但是不要立刻停写旧表,需要保持双写一段时间。

不停写旧表有两个原因:第一是因为如果读新表出现问题,还可以将读流量切回旧表。第二是因为可以进行数据校对,例如新表和旧表数据都同步至Hive,选取几天的数据进行校对,从而验证数据同步的准确性。


04 不停服_阶段4.jpg


第五阶段当读写新表一段时间之后,没有发生业务问题,可以停写旧表:


05 不停服_阶段5.jpg


3.3 代理层实现

代理层实现了新旧数据源切换,需要尽量减少业务层代码的侵入性,而适配器模式可以有效减少对业务层的侵入性。我们首先看看旧数据访问对象和业务服务:

// 订单数据对象
public class OrderDO {
    private String orderId;
    private Long price;

    public String getOrderId() {
        return orderId;
    }

    public void setOrderId(String orderId) {
        this.orderId = orderId;
    }

    public Long getPrice() {
        return price;
    }

    public void setPrice(Long price) {
        this.price = price;
    }
}

// 旧DAO
public interface OrderDAO {
    public void insert(OrderDO orderDO);
}

// 业务服务
public class OrderServiceImpl implements OrderService {

    @Resource
    private OrderDAO orderDAO;

    @Override
    public String createOrder(Long price) {
        String orderId = "orderId_123";
        OrderDO orderDO = new OrderDO();
        orderDO.setOrderId(orderId);
        orderDO.setPrice(price);
        orderDAO.insert(orderDO);
        return orderId;
    }
}

引入新数据源访问对象:

// 新数据对象
public class OrderNewDO {
    private String orderId;
    private Long price;
}

// 新DAO
public interface OrderNewDAO {
    public void insert(OrderNewDO orderNewDO);
}

适配器模式减少业务代码侵入性:

// 代理层
public class OrderDAOProxy implements OrderDAO {
    private OrderDAO orderDAO;
    private OrderNewDAO orderNewDAO;

    public OrderDAOProxy(OrderDAO orderDAO, OrderNewDAO orderNewDAO) {
        this.orderDAO = orderDAO;
        this.orderNewDAO = orderNewDAO;
    }

    @Override
    public void insert(OrderDO orderDO) {
        if(ApolloConfig.routeNewDB) {
            OrderNewDO orderNewDO = new OrderNewDO();
            orderNewDO.setPrice(orderDO.getPrice());
            orderNewDO.setOrderId(orderDO.getOrderId());
            orderNewDAO.insert(orderNewDO);
        } else {
            orderDAO.insert(orderDO);
        }
    }
}


// 业务服务
public class OrderServiceImpl implements OrderService {

    @Resource
    private OrderDAO orderDAO;
    @Resource
    private OrderNewDAO orderNewDAO;

    @Override
    public String createOrder(Long price) {
        String orderId = "orderId_123";
        OrderDO orderDO = new OrderDO();
        orderDO.setOrderId(orderId);
        orderDO.setPrice(price);
        new OrderDAOProxy(orderDAO, orderNewDAO).insert(orderDO);
        return orderId;
    }
}

4 文章总结

分库分表具有三个必须面对的问题:方案本身复杂性、本地事务失效问题、难以聚合查询问题,所以分库分表方案并非解决海量数据问题的首选。

如果必须分库分表,首先进行容量预估并选择合适的shardingKey,其次根据实际业务选择停服或者不停服方案,如果选择不停服方案,注意保持新表和旧表双写一段时间,从而验证数据准确性,希望本文对大家有所帮助。


5 延伸阅读

一种简单可落地的分布式事务方案

面试官问单表数据量大是否必须分库分表

本文转载于网络 如有侵权请联系删除

相关文章

  • 基于VuePress和github用搭建无服务器的博客、文档系统

    基于VuePress和github用搭建无服务器的博客、文档系统最近想做一个项目介绍自己的一些项目和日常的文档,让文档有个属于自己的家,https://g.xgss.net使用gitbook之后,又看到了vuepress,感觉还是挺好用的。既可以当做博客系统、文档系统,项目介绍的系统,还有丰富的插件使用。要用到的域名:http://vuepress.xgss.net(githubpages)的域名。github地址:https://github.com/funet8/vuepress.xgss.net.git什么是VuePressVuePress由两部分组成:第一部分是一个极简静态网站生成器(opensnewwindow),它包含由Vue驱动的主题系统和插件API,另一个部分是为书写技术文档而优化的默认主题,它的诞生初衷是为了支持Vue及其子项目的文档需求。简单的说它就是一个快速建设文档站点的工具,在简单配置好功能后,需要做的事情就剩下写好一个个Markdown文档,并且可以将其发布到githubpages中vuepress官网:https://vuepress.vuejs.org/z

  • annotations导入报错

    相关环境版本python3.7.10 fastapi0.63.0 Cython0.29.22复制报错文件#main.py from__future__importannotations ...... #code复制报错信息1.main.py:1:23:futurefeatureannotationsisnotdefined Traceback(mostrecentcalllast): File"/usr/local/lib/python3.7/dist-packages/Cython/Build/Dependencies.py",line1249,incythonize_one_helper returncythonize_one(*m) File"/usr/local/lib/python3.7/dist-packages/Cython/Build/Dependencies.py",line1225,incythonize_one raiseCompileError(None,pyx_file)复制2.Traceback(mostrecentc

  • java五子棋小游戏含免费源码

    游戏截图: 看一下运行效果 这里我使用的开发工具是Eclipse主要代码Main.java:publicclassMainextendsJFrame{ /* *用户登录 */ privatestaticfinallongservialVersionUID=1L; finalJLabellogoLabel=newJLabel("开心五子棋"); finalJLabellogo=newJLabel(); finalJButtonloginButton=newJButton("登陆"); finalJLabelregisterLabel=newJLabel("立即注册"); finalJLabeluserLabel=newJLabel("账号:"); finalJLabelpasswordLabel=newJLabel("密码:"); finalstaticJTextFielduserjt=newJTextField(11); finalJPasswordField

  • JAVA Thread Dump 文件分析

    JAVAThreadDump文件分析ThreadDump介绍ThreadDump是非常有用的诊断Java应用问题的工具。每一个Java虚拟机都有及时生成所有线程在某一点状态的thread-dump的能力,虽然各个Java虚拟机打印的threaddump略有不同,但是大多都提供了每个线程的所有信息,例如:线程状态、线程Id、本机Id、线程名称、堆栈跟踪、优先级。ThreadDump特点能在各种操作系统下使用能在各种Java应用服务器下使用可以在生产环境下使用而不影响系统的性能可以将问题直接定位到应用程序的代码行上(对于线上排查问题非常有用)它能帮我们解决哪些线上问题?Threaddump能帮我们定位到例如CPU峰值、应用程序中的无响应性、响应时间差、线程挂起、高内存消耗。如何抓取ThreadDump一般当服务器挂起,崩溃或者性能底下时,就需要抓取服务器的线程堆栈(ThreadDump)用于后续的分析.在实际运行中,往往一次dump的信息,还不足以确认问题。为了反映线程状态的动态变化,需要接连多次做threaddump,每次间隔10-20s,建议至少产生三次dump信息,如果每次dump都

  • django restframework serializer 增加自定义字段操作

    在使用djangorestframeworkserializer序列化在django中定义的model时,有时候我们需要额外在serializer中增加一些model中没有的字段。有两种方法实现这个目的。假设现在有一个Animal模型,其中有name,type,country字段,country为外键。我们在序列化Animal时,需要额外增加外键country的area信息。方法一修改数据库,利用model这里就不多解释,主要来说第二种,不修改django的model,直接使用SerializerMethodField(method_name=None)字段。classAnimalSerializer(serializers.ModelSerializer): country_area=serializers.SerializerMethodField() classMeta: model=Animal fields=('id','name','type','country','country_

  • java首字符大小写特殊方法

    前言今天在学习大佬手写spring核心,有一个功能是首字母小写,是使用字母的ascii编码前移实现,记录一下代码展示原版publicStringlowerFirstCase(Stringstr){        char[]chars=str.toCharArray();       //首字母小写方法,大写会变成小写,如果小写首字母会消失 //32为是char类型大小写的差数,-32是小写变大写,+32是大写变小写       chars[0]+=32;       returnString.valueOf(chars); }复制优化一下上面代码本身就是大写字母,再加32不就有问题了,先做一个判断publicStringlowerFirstCase(Stringstr){ char[]chars=str.toCharArray(); if(chars[0]>=97&&chars[0]<=122) chars[0]+=32; returnString.valueOf(chars); }复制后来发现用位运算符更牛逼/** *首字母大写 *  *@para

  • 通过源码理解Spring中@Scheduled的实现原理并且实现调度任务动态装载

    前提最近的新项目和数据同步相关,有定时调度的需求。之前一直有使用过Quartz、XXL-Job、EasyScheduler等调度框架,后来越发觉得这些框架太重量级了,于是想到了Spring内置的Scheduling模块。而原生的Scheduling模块只是内存态的调度模块,不支持任务的持久化或者配置(配置任务通过@Scheduled注解进行硬编码,不能抽离到类之外),因此考虑理解Scheduling模块的底层原理,并且基于此造一个简单的轮子,使之支持调度任务配置:通过配置文件或者JDBC数据源。Scheduling模块Scheduling模块是spring-context依赖下的一个包org.springframework.scheduling:这个模块的类并不多,有四个子包:顶层包的定义了一些通用接口和异常。org.springframework.scheduling.annotation:定义了调度、异步任务相关的注解和解析类,常用的注解如@Async、@EnableAsync、@EnableScheduling和@Scheduled。org.springframework.sch

  • 从测试到测试开发-给还在迷茫的你一点建议

    前⾔首先感谢我的群友、同学在生活和学习这条路上给予的帮助和建议,然后感谢我的老师给予这次机会把我放在平台上,最后要感谢我的领导让我在⼀个相对较大的平台去接受新的挑战和历练。本篇⽂章分享:疫情期间,我是如何从功能测试进阶测试开发,从测试⼩白到测试开发的。准备冲刺思想准备从16年⼤学毕业,参与JAVA开发培训,后来从事运维工作,再到测试工程师。我经常在想,在IT这个⼤圈,我究竟适合那个岗位?这点上很多⼈应该也迷茫过,我是要转岗做开发、还是运维、或者是产品?总觉得测试这块每天的点点点,市场也不成熟,⾃己的发展会有瓶颈。或者你已经确定,我想要做一名测试,那么⼜会引伸出来一些新的问题,什么是测试?如何做⼀名合格的测试?如何做一名优秀的测试?市场上测试技能要求这么庞⼤且繁杂,我们该如何进阶,如何蜕变?其实从功能测试到测试开发或者说再进阶测试架构,遵从金字塔模型,越往上知识点越精通,职业发展和规划越来越重要。我在年前裸辞的时候,已经确定了⾃己的方向。我要做⼀名了解前端和移动端、精通服务端能够独立负责业务、技术、架构调整,及时有效的解决企业内⼈力资源调配、测试架构选型、测试技术推⼴的问题的服务端测试开

  • 漫画:二叉树系列 第六讲(平衡二叉树)

    今日偷懒,在家忙着码代码,所以就分享一道简单点的题目~在之前的系列中,我们已经学习了二叉树的深度以及DFS,如果不会可以先查看之前的文章。今天我们将对其进行应用,直接看题目:01第110题:平衡二叉树第110题:给定一个二叉树,判断它是否是高度平衡的二叉树。本题中,一棵高度平衡二叉树定义为:一个二叉树每个节点的左右两个子树的高度差的绝对值不超过1。示例1:给定二叉树[3,9,20,null,null,15,7]3/\920/\157返回true。示例2:给定二叉树[1,2,2,3,3,null,null,4,4]1/\22/\33/\44返回false。02图解分析首先分析题目,这道题思路很简单,我们想判断一棵树是否满足平衡二叉树,无非就是判断当前结点的两个孩子是否满足平衡,同时两个孩子的高度差是否超过1。那只要我们可以得到高度,再基于高度进行判断即可。我们先复习一下之前对于树高度的求解:这里唯一要注意的是,当我们判定其中任意一个节点如果不满足平衡二叉树时,那说明整棵树已经不是一颗平衡二叉树,我们可以对其进行阻断,不需要继续递归下去。另外,需要注意的是,下面这棵并不是平衡二叉树:03代

  • jQuery实现本地input选择图片实时显示

    在写图片上传功能时,如果可以实时预览就好了,我们可以通过jQuery实现这一效果。为了更加美观,可以结合我之前写的一篇文章,给图片显示区域设置一个默认图片:HTMLimgsrc图片路径不存在,则显示一张默认图片的方法HTML代码如下:<p>     <img class="img" src="" onerror="this.src='img/default.png'" width="100" height="100"> </p> <input class="select" type="file">复制效果如下图:JS代码:当input按钮改变时,给img设置src<script>     $('.select').change(function(e) {         var _URL = window.URL || window.webkitUR

  • 百度地图JavaScript API获取用户当前经纬度和详细地理位置,反之通过详细地理位置获取当前经纬度

    前言:  前端时间刚好使用了百度地图的jsapi定位获取用户当前经纬度并获取当前详细位置和通过当前用户详细地理位置换取用户当前经纬度坐标的功能,为了方便下次找起来方便一些自己在这里记录一下,希望也能够帮助到有需要的童鞋们!解决方案:引入JavaScriptAPIv2.0SDK<scripttype="text/javascript"src="http://api.map.baidu.com/api?v=2.0&ak=您的密钥"></script>复制在页面中定义一个以ID为allmap的DIV标签:<divid="allmap"></div>避免页面中出现:UncaughtTypeError:Cannotreadproperty'gc'ofundefined复制通过SDK辅助定位获取坐标,然后在获取当前用户详细地址<scripttype="text/javascript"> varmap=newBMap.Map(&quo

  • Python定时任务

    在项目中,我们可能遇到有定时任务的需求。其一:定时执行任务。例如每天早上8点定时推送早报。其二:每隔一个时间段就执行任务。比如:每隔一个小时提醒自己起来走动走动,避免长时间坐着。今天,我跟大家分享下Python定时任务的实现方法。 1、第一种办法是最简单又最暴力。那就是在一个死循环中,使用线程睡眠函数sleep()。fromdatetimeimportdatetime importtime ''' 每个10秒打印当前时间。 ''' deftimedTask(): whileTrue: print(datetime.now().strftime("%Y-%m-%d%H:%M:%S")) time.sleep(10) if__name__=='__main__': timedTask()复制这种方法能够执行固定间隔时间的任务。如果timedTask()函数之后还有些操作,我们还使用死循环+阻塞线程。这会使得timedTask()一直占有CPU资源,导致后续操作无法执行。我建议谨重使用。2

  • 查询Lync用户登录服务器和登录客户端类型

            在实际运维LyncServer2013/SkypeforBusinessServer2015过程中,难免会要查询一些用户登录的信息做辅助判断一些事务。        例如:    1、查询某一用户登录Lync/SfB前端池那一台服务器?    2、查询某一用户最后一次注册前端池的时间?    3、查询某一用户登录Lync/SfB使用客户端类型?     4、查询某一台前端服务器有那些用户登录?         官方有一条命令可以查,但是所查询的信息很有限,很难满足个性化的需求。        官方命令: Get-CsUserPoolInfo         链接:https://technet.microsoft.com/zh-CN/library/gg398615.aspx以上提到的个性化查询都可以通过连接前端服务器数据库(rtclocal)进行查询,例如查询LyncServer2013/SkypeforBusinessServer2015为例,使用如下SQL命令:Select(cast(RE.ClientAppasvarchar(100)))asClientVers

  • 【题解笔记】PTA基础6-10:阶乘计算升级版

    题目地址:https://pintia.cn/problem-sets/14/problems/742 前言 咱目前还只能说是个小白,写题解是为了后面自己能够回顾。如果有哪些写错的/能优化的地方,也请各位多指教。(•̀ω•́) 题目描述 本题要求实现一个打印非负整数阶乘的函数,要求能处理一定大数值的阶乘。 展开查看详情 函数接口定义 voidPrint_Factorial(constintN); 复制 其中N是用户传入的参数,其值不超过1000。如果N是非负整数,则该函数必须在一行中打印出N!的值,否则打印"Invalidinput"。 裁判测试程序样例 #include<stdio.h> voidPrint_Factorial(constintN); intmain() { intN; scanf("%d",&N); Print_Factorial(N); return0; } /*你的代码将被嵌在这里*/ 复制 输入样例 15 复制 输出样例 1307674368000 复制 限制 限制内容 限制条件 代码长度限制 16KB 时间限制

  • Spark大数据处理框架入门(单机版)

    导读 引言 环境准备 安装步骤 1.下载地址 2.开始下载 3.解压spark 4.配置环境变量 5.配置spark-env.sh 6.启动spark服务 7.测试spark 感谢您的阅读,预计阅读时长3min。智客工坊出品必属精品。 引言 2012年,UCBerkelye的ANPLab研发并开源了新的大数据处理框架Spark。其核心思想包括两方面:一方面对大数据处理框架的输入/输出、中间数据进行建模,将这些数据抽象为统一的数据结构,命名为弹性分布式数据集(ResilentDistributedDataset,RDD),并在此数据结构上构建了一系列通用的数据操作,使得用户可以简单地实现复杂的数据处理流程;另一方面采用基于内存的数据聚合、数据缓存等机制来加速应用执行,尤其适用于迭代和交互式应用。Spark采用EPFL大学研发的函数式编程语言Scala实现,并且提供了Scala、Java、Python、R四种语言的接口,以方便开发者适用熟悉的语言进行大数据应用开发。 话不多说,现在就开始我们的Spark之旅吧! 一环境准备: 服务器 配置 单机 文件目录 Ce

  • python将时间格式化为(xx年xx月xx日)

    importdatetimestr1=datetime.datetime.now().strftime("%Y-%m-%d")如上的xx-xx-xx格式是可以直接格式化的。但是对于xx年xx月xx日的格式化要借助format函数str2=datetime.datetime.now().strftime("%Y{y}%m{m}%d{d}").fromat(y="年",m="月",d="日")

  • 计算机网络之应用层

    应用层 1、域名系统DNS 域名系统DNS(DomainNameSystem)是一个联机分布式数据库系统,采用C/S方式,提供了主机名和IP地址之间相互转换的服务。这里的分布式数据库是指,每个站点只保留它自己的那部分数据。 1.1主要任务 一个由分层的DNS服务器实现的分布式数据库; 一个是的主机能够查询分布式数据库的应用层协议。 DNS协议运行在UDP之上,使用53端口。 DNS通常由其他的应用层协议HTTP、FTP、SMTP所使用,将用户提供的主机名解析为IP地址。 1.2应用场景 DNS协议是应用层协议。 使用客户-服务器模式运行在通信的端系统之间; 在通信的端系统之间通过下面的端到端运输协议来传送DNS报文。 1.3DNS运行做法 运行在用户主机上的一个浏览器请求URLhttps://www.cnblogs.com/njuptzheng/时会发生什么? 同一台用户主机上运行着DNS应用的客户端; 浏览器从上述URL中抽取主机名www.cnblogs.com,并将这台主机名传送给DNS应用的客户端; DNS客户向DNS服务器发送一个包含主机名的请求; DNS客户最终会收

  • 【译】gRPC-Web for .NET now available

      .NET的gRPC-Web现在正式发布了。我们在一月份发布了实验版,从那时起,我们就根据早期的用户反馈进行着改进。   有了这个版本,gRPC-Web就变成了grpc-dotnet项目的一个完全受支持的组件,它已经准备就绪。现在可以通过gRPC-Web和.NET在浏览器中使用gRPC。 开始吧   刚接触gRPC的开发者,应该学习下微软的《教程:在ASP.NETCore中创建gRPC客户端和服务器》,该教程介绍如何使用.NET创建一个gRPCclient和server。   如果你已经有一个gRPC应用,那么《在浏览器应用中使用gRPC》将展示如何将gRPC-Web添加到.NETgRPCserver。 gRPC和gRPC-Web是什么   gRPC是一个现代的高性能RPC(RemoteProcedureCall)框架。gRPC基于HTTP/2、ProtocolBuffers和其他基于标准的现代技术。gRPC是一种开放标准,受到许多编程语言的支持,包括.NET。   目前在浏览器中实现gRPCHTTP/2规范是不可能的,因为没有对请求进行足够细粒度控制的浏览器API。gRPC-Web

  • Web前端(12)_注释

    <!--我是注释-->复制    看看这家公司的程序员给些的注释,哈哈  

  • UVa 297 Quadtrees -SilverN

    Aquadtreeisarepresentationformatusedtoencodeimages.Thefundamentalideabehindthequadtreeisthatanyimagecanbesplitintofourquadrants.Eachquadrantmayagainbesplitinfoursubquadrants,etc.Inthequadtree,theimageisrepresentedbyaparentnode,whilethefourquadrantsarerepresentedbyfourchildnodes,inapredeterminedorder.   Ofcourse,ifthewholeimageisasinglecolor,itcanberepresentedbyaquadtreeconsistingofasinglenode.Ingeneral,aquadrantneedsonlytobesubdividedifitconsistsofpixelsofdifferentcolors.Asaresult,thequadtr

  • EasyRepro与测试自动化( 一) 概览

    EasyRepro是一个框架,允许在特定的Dynamics365组织上执行自动化UI测试。你可以使用它来自动化冒烟测试、回归测试和负载测试等。该框架是由开源项目Selenium构建的,Selenium在业界的各种项目和应用程序中有着广泛应用。 整个EasyRepro框架都是开源的,可在GitHub上获得。本文的目的是逐步介绍EasyRepro框架的设置。本文假设读者已经掌握某些概念,例如如何在VisualStudio中使用单元测试,如何下载NuGet软件包以及从GitHub克隆repo。   本文链接:https://www.cnblogs.com/hhelibeb/p/13556244.html 英文原文:TestAutomationandEasyRepro:01-OverviewandGettingStarted   现在,你已经对EasyRepro的用处有了基本的了解,可能想开始使用它了。 启动和运行EasyRepro非常简单,因为该框架在设计时就考虑了灵活性和敏捷性。但是,像使用其他实用程序一样,要开始使用EasyRepro,需要学习一些东西和克服一些障碍。

相关推荐

推荐阅读