首页 文章 精选 留言 我的

精选列表

搜索[入门],共9227篇文章
优秀的个人博客,低调大师

SpringBoot2全家桶,快速入门学习开发网站教程

说明 本系列文章带大家玩转SpringBoot2。 但是学习是一个循序渐进的过程,所以该文章将分为几个小章节讲述。 并且在学习SpringBoot之前需要一定的基础知识 SpringMVC、Spring、MyBatis基础知识 Maven MySQL、Redis 本文章代码建立的环境基础 注:由于环境不同可能会导致代码运行效果不同,请同步环境 开发环境 名称 版本 JDK jdk-12.0.2 IDE Eclipse2019.09 Databases MySQL_v8.0.18 SpringBoot v2.2.2 章节 1.SpringBoot2初体验 2.SpringBoot2编写第一个Controller 3.SpringBoot2整合MyBatis 4.Springboot2将默认的数据库连接池hikari替换为druid 5.SpringBoot2整合Redis 6.SpringBoot2配置默认Tomcat服务器 7.SpringBoot2更换Tomcat为Jetty 8.SpringBoot2整合Thymeleaf

优秀的个人博客,低调大师

【从入门到放弃-ZooKeeper】ZooKeeper入门

前言 ZooKeeper是一个分布式服务协调框架,可以用来维护分布式配置信息、服务注册中心、实现分布式锁等。在Hbase、Hadoop、kafka等项目中都有广泛的应用。随着分布式、微服务的普及,ZooKeeper已经成为我们日常开发工作中无法绕过的一环,本文将从安装配置到最基础的使用入手,对其进行介绍。 安装部署 下载 在 http://mirror.bit.edu.cn/apache/zookeeper/stable/ 下载推荐的stable版本。可以直接下载编译好的bin文件。目前最新的stable版本是3.5.5 解压 将下载的文件解压至工作目录,我的工作目录是/var/workspace/zookeeper tar -xzvf apache-zookeeper-3.5.5-bin.tar.gz -C /var/workspace/zo

优秀的个人博客,低调大师

es 入门

**注意事项-------使用ealsticsearch要配置java的开发环境JDK(1.8以上) ealsticsearch: 索引(Index) 类型(type) 文档(Document) 字段(Fields) 关系型数据库: 数据库 表 行 列 from elasticsearch import Elasticsearch 默认host为localhost,port为9200.但也可以指定host与port es = Elasticsearch("http://127.0.0.1:9200") 插入数据,index,doc_type名称可以自定义,id可以根据需求赋值,body为内容 es.index(index="my_index",doc_type="test_type",id=0,body={"name":"python","addr":"深圳"})es.index(index="my_index",doc_type="test_type",id=1,body={"name":"java","addr":"北京"}) 同样是插入数据,create() 方法需要我们指定 id 字段来唯一标识该条数据,而 index() 方法则不需要,如果不指定 id,会自动生成一个 id es.create(index="my_index",doc_type="test_type",id=1,body={"name":"python","addr":"深圳"}) 删除指定的index、type、id的文档 es.delete(index='indexName', doc_type='typeName', id=1) 删除index es.indices.delete(index='news', ignore=[400, 404])query = {'query': {'match_all': {}}}# 查找所有文档query1 = {'query': {'match': {'sex': 'famale'}}}# 删除性别为女性的所有文档query2 = {'query': {'range': {'age': {'lt': 11}}}}# 删除年龄小于11的所有文档query3 = {'query': {'term': {'name': 'jack'}}}# 查找名字叫做jack的所有文档 删除所有文档 es.delete_by_query(index="my_index",doc_type="test_type",body=query) get:获取指定index、type、id所对应的文档 es.get(index="my_index",doc_type="test_type",id=1) search:查询满足条件的所有文档,没有id属性,且index,type和body均可为None result = es.search(index="my_index",doc_type="test_type",body=query)print(result'hits'[0])# 返回第一个文档的内容 update:更新指定index、type、id所对应的文档 更新的主要点: 1. 需要指定 id 2. body={"doc": } , 这个doc是必须的 es.update(index="my_index",doc_type="test_type",id=1,body={"doc":{"name":"python1","addr":"深圳1"}}) ---查询所有数据# 搜索所有数据es.search(index="my_index",doc_type="test_type") 或者 body = { "query":{ "match_all":{} }}es.search(index="my_index",doc_type="test_type",body=body) ---term与terms term body = { "query":{ "term":{ "name":"python" } }} 查询name="python"的所有数据 es.search(index="my_index",doc_type="test_type",body=body) terms body = { "query":{ "terms":{ "name":[ "python","android" ] } }} 搜索出name="python"或name="android"的所有数据 es.search(index="my_index",doc_type="test_type",body=body) ---match与multi_match match:匹配name包含python关键字的数据 body = { "query":{ "match":{ "name":"python" } }} 查询name包含python关键字的数据 es.search(index="my_index",doc_type="test_type",body=body) multi_match:在name和addr里匹配包含深圳关键字的数据 body = { "query":{ "multi_match":{ "query":"深圳", "fields":["name","addr"] } }} 查询name和addr包含"深圳"关键字的数据 es.search(index="my_index",doc_type="test_type",body=body) ---idsbody = { "query":{ "ids":{ "type":"test_type", "values":[ "1","2" ] } }} 搜索出id为1或2d的所有数据 es.search(index="my_index",doc_type="test_type",body=body) ---复合查询boolbody = { "query":{ "bool":{ "must":[ { "term":{ "name":"python" } }, { "term":{ "age":18 } } ] } }} 获取name="python"并且age=18的所有数据 es.search(index="my_index",doc_type="test_type",body=body) ---切片式查询body = { "query":{ "match_all":{} } "from":2 # 从第二条数据开始 "size":4 # 获取4条数据} 从第2条数据开始,获取4条数据 es.search(index="my_index",doc_type="test_type",body=body) ---范围查询 body = { "query":{ "range":{ "age":{ "gte":18, # >=18 "lte":30 # <=30 } } }} 查询18<=age<=30的所有数据 es.search(index="my_index",doc_type="test_type",body=body) ---前缀查询body = { "query":{ "prefix":{ "name":"p" } }} 查询前缀为"赵"的所有数据 es.search(index="my_index",doc_type="test_type",body=body) ---通配符查询 body = { "query":{ "wildcard":{ "name":"*id" } }} 查询name以id为后缀的所有数据 es.search(index="my_index",doc_type="test_type",body=body) ---排序body = { "query":{ "match_all":{} } "sort":{ "age":{ # 根据age字段升序排序 "order":"asc" # asc升序,desc降序 } }} ---filter_path响应过滤 只需要获取_id数据,多个条件用逗号隔开 es.search(index="my_index",doc_type="test_type",filter_path=["hits.hits._id"]) 获取所有数据 es.search(index="my_index",doc_type="test_type",filter_path=["hits.hits._*"]) ---count执行查询并获取该查询的匹配数 获取数据量 es.count(index="my_index",doc_type="test_type") ---度量类聚合获取最小值body = { "query":{ "match_all":{} }, "aggs":{ # 聚合查询 "min_age":{ # 最小值的key "min":{ # 最小 "field":"age" # 查询"age"的最小值 } } }} 搜索所有数据,并获取age最小的值 es.search(index="my_index",doc_type="test_type",body=body) ---获取最大值body = { "query":{ "match_all":{} }, "aggs":{ # 聚合查询 "max_age":{ # 最大值的key "max":{ # 最大 "field":"age" # 查询"age"的最大值 } } }} 搜索所有数据,并获取age最大的值 es.search(index="my_index",doc_type="test_type",body=body) ---获取和body = { "query":{ "match_all":{} }, "aggs":{ # 聚合查询 "sum_age":{ # 和的key "sum":{ # 和 "field":"age" # 获取所有age的和 } } }} 搜索所有数据,并获取所有age的和 es.search(index="my_index",doc_type="test_type",body=body) ---获取平均值body = { "query":{ "match_all":{} }, "aggs":{ # 聚合查询 "avg_age":{ # 平均值的key "sum":{ # 平均值 "field":"age" # 获取所有age的平均值 } } }} 搜索所有数据,获取所有age的平均值 es.search(index="my_index",doc_type="test_type",body=body)

优秀的个人博客,低调大师

Matplotlib 入门

最近一直在学习有关机器学习的内容,发现有关可视化的内容无法绕过,在网上搜索了很久,看了一些中英文的资料和书,这里主要是记录下来,主要供自己进一步学习之用。 本文适合零基础,但你需要对Python, Numpy, Pandas 有基本的了解。 首先完成以下的中文翻译资料的学习,原作者的github链接也一并奉上,你可以 clone 下来。学习这个课程的过程中,你需要了解的基本API函数: 完成了这个学习, 使用面向对象界面画图的一般步骤就是 建立一个 Figure 对象 在这个 Figure 里建立一个或n个 Axes 对象 这些 Axes 里分别画图、装饰 我们画完图以后一般要加上标题、图例这些元素。这些功能散布在 Matplotlib API 的各处 加标题可以使用 Axes 对象的 set_title 方法。坐标轴的标签可以使用 Axes 对象的 set_xlabel 和 set_ylabel 方法。图例可以使用 Axes 对象的 legend 方法创建。这 个函数会使用 Line2D 对象的 label 属性。注意 上一节最后的那一段代码,Ax.plot() 这一行我们并没有使用 plot 的返回值,实际上它的返回值正是 Line2D 对象的列表,所以我们可以这样设置 label:Ax.plot(X1, Y1, label="blabla") 也可以这样Lines = Ax.plot(X1, Y1) Lines[0].set_label("blabla") 如果我们在一个 plot 调用里画多条线,同时设 label,这些线都会有相同的 label。设置完这些以后,只需调用 Ax.legend(),图例就会出现在图的右上角。如果我们使用上一节 的那个例子,右上角并不是放图例的好地方,这时我们可以使用 legend 的 loc参数。这个参数可以是一个字符串, 比如"right","lower left" 之类,也可以是一 个整数,具体定义请见 官方文档。这里我们可以简单地使用 0 或者 "best" 让 Matplotlib 自动选择位置,Ax.legend(loc=0) 现在我们的图基本成型,可以插入到文档里了。这时我们可能想调整图的大小, 我们可以修改 Figure 构造函数的调用,Fig = Plt.figure(figsize=(4,3)) 这样图的大小就会是宽 4 高 3,单位传说是英寸。也可以使用 Fig 的 set_figwidth 和 set_figheight 方法。如果你把图的尺寸改得比较小的话,可能会 发生一件非常奇妙的事,就是坐标轴的标签被图的边界遮住了,或者干脆不见了。 解决办法就是调整绘图区域的四个边界在图中的位置,比如下面的代码Fig.subplots_adjust(left=0.15, top=0.9) 把绘图区域的左边界放在图左边 15% 的位置,上边界放在高的 90% 的位置。Matplotlib 中的文本 Matplotlib 有很强的文本功能。它可以处理普通的文本,也可以使用 LaTeX 渲 染数学公式。Matplotlib 还带了一个 LaTeX 引擎,可以在系统里没有 LaTeX 的 时候渲染数学公式。以下代码使用默认字体(貌似是 Verdana)显示 x 轴的标题Ax.set_xlabel("x") 如果你要使用 LaTeX 来排这个标题,只需要使用 Python raw string,并把标 题放在数学模式里,Ax.set_xlabel(r" 𝑥x ") 注意即便是使用 raw string,不在数学模式里的部分仍然不会使用 LaTeX。Matplotlib 的 Axes 类有一个 text 方法,可以 在一个 Axes 对象的任意位置放置一个文本。 Figure 也有一个 figtext 方法用来在 Figure 对象的任意位置放置文本。具体用法请见 官方文档。 Axes 对象还有一个超级强大的 annotate 方法, 可以满足你对标注的最变态需求。要改变文本的默认字体,需要使用 pyplot 模块的 rc 函数,Plt.rc("font", family="Helvetica") 不过如果你指定的字体是 OpenType 的话,貌似不能嵌入到 PDF 里…​ 请参考: 1)github - https://github.com/rougier/matplotlib-tutorial 2) Translation - https://liam.page/2014/09/11/matplotlib-tutorial-zh-cn/ 3) https://www.cnblogs.com/wei-li/archive/2012/05/23/2506940.html4) https://journals.plos.org/ploscompbiol/article?id=10.1371/journal.pcbi.1003833 以下为草稿:http://liyangbit.com/pythonvisualization/matplotlib-top-50-visualizations/

优秀的个人博客,低调大师

Jmeter 入门

Jmeter的初步认识 一、 接口测试 一般的测试包含七步: 新建请求 添加线程组 添加http请求 配置http参数 添加cookie管理、header管理 添加结果树 在点击运行 查看运行结果 参数在前后端分离一般用json格式,也可以选择html(HTMl Source Formatted)或者其他 二、 压力测试 配置线程组 参数说明: -. 线程数:虚拟用户数。一个虚拟用户占用一个进程或线程。多少虚拟用户就设置多少个线程数。 -. Rame-Up Period: 表示线程数多长时间全部启动。即准备时长:如果线程数是15,准备时长为3,,也就是每秒钟启动5个线程。 -. 循环次数:这个设置不会改变并发数,可以延长并发时间。总请求数=线程数*循环次数 -. 调度器:设置压测的启动时间、结束时间、持续时间和启动延迟时间。

优秀的个人博客,低调大师

Prometheus 入门

文章首发于公众号《程序员果果》地址 : https://mp.weixin.qq.com/s/BjZyNcWEgflJMnjfceiluw 简介 Prometheus 是一套开源的系统监控报警框架。它启发于 Google 的 borgmon 监控系统,由工作在 SoundCloud 的 google 前员工在 2012 年创建,作为社区开源项目进行开发,并于 2015 年正式发布。 特点 作为新一代的监控框架,Prometheus 具有以下特点: 强大的多维度数据模型: 时间序列数据通过 metric 名和键值对来区分。 所有的 metrics 都可以设置任意的多维标签。 数据模型更随意,不需要刻意设置为以点分隔的字符串。 可以对数据模型进行聚合,切割和切片操作。 支持双精度浮点类型,标签可以设为全 unicode。 灵活而强大的查询语句(PromQL):在同一个查询语句,可以对多个 metrics 进行乘法、加法、连接、取分数位等操作。 易于管理: Prometheus server 是一个单独的二进制文件,可直接在本地工作,不依赖于分布式存储。 高效:平均每个采样点仅占 3.5 bytes,且一个 Prometheus server 可以处理数百万的 metrics。使用 pull 模式采集时间序列数据,这样不仅有利于本机测试而且可以避免有问题的服务器推送坏的 metrics。 可以采用 push gateway 的方式把时间序列数据推送至 Prometheus server 端。 可以通过服务发现或者静态配置去获取监控的 targets。 有多种可视化图形界面。 易于伸缩。 组成及架构 Prometheus 生态圈中包含了多个组件,其中许多组件是可选的: Prometheus Server: 用于收集和存储时间序列数据。 Client Library: 客户端库,为需要监控的服务生成相应的 metrics 并暴露给 Prometheus server。当 Prometheus server 来 pull 时,直接返回实时状态的 metrics。 Push Gateway: 主要用于短期的 jobs。由于这类 jobs 存在时间较短,可能在 Prometheus 来 pull 之前就消失了。为此,这次 jobs 可以直接向 Prometheus server 端推送它们的 metrics。这种方式主要用于服务层面的 metrics,对于机器层面的 metrices,需要使用 node exporter。 Exporters: 用于暴露已有的第三方服务的 metrics 给 Prometheus。 Alertmanager: 从 Prometheus server 端接收到 alerts 后,会进行去除重复数据,分组,并路由到对收的接受方式,发出报警。常见的接收方式有:电子邮件,pagerduty,OpsGenie, webhook 等。 一些其他的工具。 下图为 Prometheus 官方文档中的架构图: 从上图可以看出,Prometheus 的主要模块包括:Prometheus server, exporters, Pushgateway, PromQL, Alertmanager 以及图形界面。 其大概的工作流程是: Prometheus server 定期从配置好的 jobs 或者 exporters 中拉 metrics,或者接收来自Pushgateway 发过来的 metrics,或者从其他的 Prometheus server 中拉 metrics。 Prometheus server 在本地存储收集到的 metrics,并运行已定义好的 alert.rules,记录新的时间序列或者向 Alertmanager 推送警报。 Alertmanager 根据配置文件,对接收到的警报进行处理,发出告警。在图形界面中,可视化采集数据。 相关概念 下面将对 Prometheus 中的数据模型(时间序列),metric 类型,instance 和 jobs等概念进行介绍。 数据模型 Prometheus 中存储的数据为时间序列,是由 metric 的名字和一系列的标签(键值对)唯一标识的,不同的标签则代表不同的时间序列。 metric 名字:该名字应该具有语义,一般用于表示 metric 的功能,例如:http_requests_ total, 表示 http 请求的总数。其中,metric 名字由 ASCII 字符,数字,下划线,以及冒号组成,且必须满足正则表达式 a-zA-Z_:*。 标签:使同一个时间序列有了不同维度的识别。例如 http_requests_total{method="Get"} 表示所有 http 请求中的 Get 请求。当 method="post" 时,则为新的一个 metric。标签中的键由 ASCII 字符,数字,以及下划线组成,且必须满足正则表达式 a-zA-Z_:*。 样本:实际的时间序列,每个序列包括一个 float64 的值和一个毫秒级的时间戳。 格式:{=, …},例如:http_requests_total{method="POST",endpoint="/api/tracks"}。 Metrics种类 Prometheus客户端库提供了四种核心Metrics类型。 Counter(计数器) 说明:Counter是一个累积度量,它表示一个单调递增的 Metrics,其值只能在重启时递增或重置为零 场景:可以使用Counter来表示http的请求数、已完成的任务数或错误数、下单数。 Gauge(测量仪) 说明:当前值的一次快照(snapshot)测量,可增可减。 场景:磁盘使用率,当前同时在线用户数。 Histogram(直方图) 说明:通过区间统计样本分布。 场景:请求延迟时间的统计。例如统计 0~200ms、200ms~400ms、400ms~800ms 区间的请求数有多。 Summary(汇总) 说明:根据样本统计出百分位。 场景:请求延迟时间的统计。例如统计 95%的请求延迟 < xxx ms ,99%的请求延迟 < xxx ms instance 和 jobs 在Prometheus术语中,你可以scrape(刮擦)的端点称为 实例,通常对应于单个进程。一组同种类型的 instances(主要用于保证可扩展性和可靠性),例如:具有四个复制instances(实例)的API服务器job作业: job: api-server instance 1: 1.2.3.4:5670 instance 2: 1.2.3.4:5671 instance 3: 5.6.7.8:5670 instance 4: 5.6.7.8:5671 当Prometheus scrape(刮擦)目标时,它会自动在scrape的时间序列上附加一些标签,用来识别scrape的目标。 job:目标所属的已配置job名称。 instance::已刮擦的目标URL 的一部分。 对于每次实例 scrape(刮取,Prometheus都会在以下时间序列中存储样本: up{job="<job-name>", instance="<instance-id>"}:1如果实例是健康的,即可达,或者0刮擦失败。 scrape_duration_seconds{job="<job-name>", instance="<instance-id>"}:刮擦持续时间。 scrape_samples_post_metric_relabeling{job="<job-name>", instance="<instance-id>"}:应用度量标准重新标记后剩余的样本数。 scrape_samples_scraped{job="<job-name>", instance="<instance-id>"}:目标暴露的样本数。 scrape_series_added{job="<job-name>", instance="<instance-id>"}:该刮擦中新系列的大致数量。v2.10中的新功能。 up时间序列对于实例可用性监视非常有用。 安装和配置 安装 你可以在官网 https://prometheus.io/download/ 下载 安装包,解压后使用。为了方便,我使用docker 镜像的方式 运行Prometheus。 docker run --name prometheus -d -p 9090:9090 prom/prometheus 浏览器输入http://localhost:9090 ,访问 Prometheus 的 Web UI: 点击菜单栏 “Status” 下的 Targets ,界面如下: 可以看大Prometheus 自身 metrics 处于UP状态 ,说明 安装成功。 配置 Prometheus 的配置文件 prometheus.yml 内容如下: # 全局设置,可以被覆盖 global: scrape_interval: 15s evaluation_interval: 15s rule_files: # - "first.rules" # - "second.rules" scrape_configs: - job_name: prometheus static_configs: - targets: ['localhost:9090'] 该global块控制 Prometheus 的全局配置。我们有两种选择。第一个,scrape_interval控制Prometheus 刮擦目标的频率。你可以为单个目标覆盖此值。在这种情况下,全局设置是每15秒刮一次。该evaluation_interval选项控制普罗米修斯评估规则的频率。Prometheus 使用规则创建新的时间序列并生成警报。 该rule_files块指定我们希望 Prometheus 加载的任何规则的位置。现在我们没有规则。 最后一个块scrape_configs控制 Prometheus 监视的资源。由于 Prometheus 还将自己的数据公开为HTTP端点,因此它可以抓取并监控自身的健康状况。在默认配置中有一个名为 prometheus 的job,它抓取 prometheus 服务器 公开的时间序列数据。该作业包含一个静态配置的目标,即端口9090上的本地主机。返回的时间序列数据将详细说明Prometheus服务器的状态和性能。 实验 Prometheus HTTP 度量模拟器 为了演示 Prometheus 的简单使用,这里运行一个 Prometheus HTTP 度量模拟器。模拟一个简单的HTTP微服务,生成Prometheus Metrics,通过 docker 运行。 docker run -p 8080:8080 pierrevincent/prom-http-simulator:0.1 它在/metrics端点下公开以下Prometheus指标: http_requests_total:请求计数器,标签endpoint和status http_request_duration_milliseconds:请求延迟直方图 可以开启流量高峰模式,更改流量高峰模式可以通过以下方式完成: # ON curl -X POST http://127.0.0.1:8080/spike/on # OFF curl -X POST http://127.0.0.1:8080/spike/off # RANDOM curl -X POST http://127.0.0.1:8080/spike/random 错误率默认为1%。它可以更改为0到100之间的数字: # 例如将错误率设置为50% curl -H 'Content-Type: application/json' -X PUT -d '{"error_rate": 50}' http://127.0.0.1:8080/error_rate 修改Prometheus配置 需要将 HTTP 度量模拟器 的 metrics端点 配置到 Prometheus的配置文件 prometheus.yml 中。 创建一个 prometheus.yml 文件 内容如下: global: scrape_interval: 5s evaluation_interval: 5s scrape_timeout: 5s scrape_configs: - job_name: 'prometheus' static_configs: - targets: ['localhost:9090'] - job_name: 'http-simulator' metrics_path: /metrics static_configs: - targets: ['172.16.1.232:8080'] 通过docker up 命令替换 容器中的配置文件: docker cp prometheus.yml prometheus:/etc/prometheus/ 重启容器: docker restart prometheus 访问 http://localhost:9090/targets ,发现已经出现了 target “http-simulator” ,并且为UP状态。 查询 请求率(Request Rate)查询 查询http请求数 http_requests_total{job="http-simulator"} 查询成功login请求数 http_requests_total{job="http-simulator", status="200", endpoint="/login"} 查询成功请求数,以endpoint区分 http_requests_total{job="http-simulator", status="200"} 查询总成功请求数 sum(http_requests_total{job="http-simulator", status="200"}) 查询成功请求率,以endpoint区分 rate(http_requests_total{job="http-simulator", status="200"}[5m]) 查询总成功请求率 sum(rate(http_requests_total{job="http-simulator", status="200"}[5m])) 延迟分布(Latency distribution)查询 查询http-simulator延迟分布 http_request_duration_milliseconds_bucket{job="http-simulator"} 查询成功login延迟分布 http_request_duration_milliseconds_bucket{job="http-simulator", status="200", endpoint="/login"} 不超过200ms延迟的成功login请求占比 sum(http_request_duration_milliseconds_bucket{job="http-simulator", status="200", endpoint="/login", le="200"}) / sum(http_request_duration_milliseconds_count{job="http-simulator", status="200", endpoint="/login"}) 成功login请求延迟的99百分位 histogram_quantile(0.99, rate(http_request_duration_milliseconds_bucket{job="http-simulator", status="200", endpoint="/login"}[5m])) 上面给出的这些查询表达式,在 prometheus 的 查询界面上自行测试下 ,这里就不一一测试了, 总结 本篇对 Prometheus 的组成,架构和基本概念进行了介绍,并实例演示了 Prometheus 的查询表达式的应用。本篇是 Prometheus 系列的第一篇, 后续还会有Prometheus与其他图形界面的集成,与 springboot 应用的集成等 。 参考 https://prometheus.io/docs/introduction/overview/https://www.ibm.com/developerworks/cn/cloud/library/cl-lo-prometheus-getting-started-and-practice/index.html

优秀的个人博客,低调大师

MyCAT入门

MyCAT 1.什么是MyCAT MyCAT是基于阿里的cobar实现的数据库中间件产品。 2.什么是数据库中间件 就是介于数据库和应用之间,进行数据处理与交互的中间服务。有了数据库中间件,应用只需要集中处理业务,大量的通用的数据聚合,事务,数据源切换都由数据库中间件处理。 3.MyCAT的主要作用 1.单纯的读写分离,支持读写分离,主从切换; 2.分表分库,对于超过1000万的表进行分片,最大支持1000亿的单表分片 3.多租户应用,每个应用一个库,但应用程序mycat,实现多租户化 4.报表系统,借助mycat分表能力,处理大规模报表统计 5.替代Hbase,分析大数据 4.MyCAT基本元素 逻辑库 数据库中间件是一个或多个数据库集群构成的逻辑库。他并不是只有一个数据库,而是有多个库的聚集。 逻辑表 1.逻辑表 在分布式数据库系统中,对应用来说,读写数据的表就是逻辑表。逻辑表可以是数据切分后,分布在一个或多个分片库,也可以不做数据切分,不分片,只由一个表构成。 2.分片表 分片表,是指那些原有的很大数据的表,需要切分到多个数据库的表。 3.非分片表 一个数据库中并不是所有的表都很大,某些表可以不用进行切分。非分片表,既是数据量比较小的表 4.ER表 子表的记录与所关联的父表记录存放在同一数据分片上,即字表依赖于父表成为ER表。 5.全局表 一些常常很少会改变的表成为全局表(例如字典表),全局表将在每个数据分片上拷贝一份,且要保证数据相同。此时使用join操作时,将会大大提高效率。 分片节点 数据切分后,一个大表被分到不同分片数据库上面,每个表分片所在的数据库就是分片节点。(dataNode) 节点主机 数据切分后,每个分片节点不一定会都独占一个物理机器,同一物理机器上面可以有多个分片数据库,这样一个或多个分片节点所在的物理机器就是节点主机。为了避免单节点主机并发数量限制,尽量将读写压力高的节点,均匀分布在不同的节点主机。 分片规则 一个大表被分成若干个分片表,就需要一定的规则。这样按照业务将数据分到某个分片的规则就是分片规则。 全局序列号 数据切分后,原有关系数据库中的主键约束在分布式条件下将无法使用,因此需要引入外部机制保证数据唯一性标识,这种保证全局数据唯一标识的机制就是全局序列号 5.MyCAT安装 下载地址:[link](http://dl.mycat.io/1.6.6.1/) ###1.Linux系统 - 到mycat项目主页下载linux安装包。 - 将解压后文件放到usr/local/mycat目录下 - mycat解压后目录如下 bin程序目录 conf配置文件存放目录 lib存放mycat项目依赖包目录 logs存放mycat日志目录 - mycat启动 vi /etc/profile 在系统环境变量文件中增加 MYCAT_HOME=/usr/local/mycat 执行source /etc/profile命令,使环境变量生效 经过以上两个步骤的配置后,就可以到/usr/local/mycat/bin目录下执行:/mycat start启动服务。 ###2.Windows系统 mycat在Windows中部署时,建议将解压后的目录放在根目录中,尽量在路径中不要包含中文路径。如:D:\Mycat-server-1.6-win\ 命令行启动方式: 从cmd命令中将路径切换到bin目录下,执行startup_nowrap.bat即可启动mycat服务。(mycat需要java环境支持,需先要求已有节哀吧、va环境。并且版本必须在1.7以上)。 ###2.基于zk的启动 ...... ...... ......

优秀的个人博客,低调大师

Protobuf 入门

IDEA中的Protobuf的插件的使用 首先File->setting中找到plugins 然后安装插件 安装完成后重启IDEA就可以了 测试: 在src/main/下建立proto文件夹,并在其中建立以proto为后缀文件 然后将如下的proto协议写进去 syntax = "proto3"; message SearchRequest { string query = 1; int32 page_number = 2; int32 result_per_page = 3; } 然后maven中添加 <properties> <grpc.version>1.6.1</grpc.version> <protobuf.version>3.3.0</protobuf.version> </properties> <dependencies> <dependency> <groupId>io.grpc</groupId> <artifactId>grpc-netty</artifactId> <version>${grpc.version}</version> <scope>provided</scope> </dependency> <dependency> <groupId>io.grpc</groupId> <artifactId>grpc-protobuf</artifactId> <version>${grpc.version}</version> <scope>provided</scope> </dependency> <dependency> <groupId>io.grpc</groupId> <artifactId>grpc-stub</artifactId> <version>${grpc.version}</version> <scope>provided</scope> </dependency> <dependency> <groupId>com.google.protobuf</groupId> <artifactId>protobuf-java</artifactId> <version>${protobuf.version}</version> </dependency> </dependencies> <build> <extensions> <extension> <groupId>kr.motd.maven</groupId> <artifactId>os-maven-plugin</artifactId> <version>1.5.0.Final</version> </extension> </extensions> <plugins> <plugin> <groupId>org.xolstice.maven.plugins</groupId> <artifactId>protobuf-maven-plugin</artifactId> <version>0.5.0</version> <configuration> <protocArtifact>com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier}</protocArtifact> <pluginId>grpc-java</pluginId> <pluginArtifact>io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}</pluginArtifact> </configuration> <executions> <execution> <goals> <goal>compile</goal> <goal>compile-custom</goal> </goals> </execution> </executions> </plugin> </plugins> </build> 等待下载完成后,然后点击maven中的compile插件,运行完,如果配置正确,在项目的target的目录下就生成了我们需要的类文件了 好了介绍到这里,IDEA中使用protobuf插件就完成了,下面是关于protobuf的简介和一些详细内容 简介 Google Protocol Buffer 简称Protobuf是Google公司内部的开源项目,可用于RPC系统和持续数据存储系统 是一种轻便高效的结构化数据存储格式,可用于结构化数据串行化,或者序列化,非常适合数据存储或RPC数据交换格式,可用于通讯协议,数据存储等领域,并且是平台无关的 定义数据类型 如上在IDEA中运行的protobuf文件,我们拿这个来说 syntax = "proto3"; message SearchRequest { string query = 1; int32 page_number = 2; int32 result_per_page = 3; } syntax 指定protobuf的版本,必须是文件的第一行 message 代表这是一个消息,SearchRequest消息中指定了三个字段,每一个字段对应于要包含在这种类型的消息中的数据,每一个字段都有一个名称和类型,比如上面的int32 page_number = 2;,名称就是page_number,而类型就是int32,注意等号后面的不是默认值,而可以认为他是此page_number字段的身份证,不可重复,这些数字用于以二进制格式标识您的字段,一旦消息类型被使用就不应该被更改,需要注意的是1到15范围内的字段编号需要一个字节来编码,而16到2047范围内的字段编号需要两个字节,所以经常使用的应该为其编码为1到15之间,这些数字的范围是[1, 536870911],其中19000~19999是为协议缓冲区实现而保留的,不可以使用,否则报错 标量值类型 即int32 page_number = 2;中的int32叫做标量值类型 下面是标量值类型以及生成的java中的对应类型 类型 说明 对应类型 double null double float null float int32 使用可变长度编码,编码负数效率低下,如果你的字段可能有负值,请改用sint32 int int64 使用可变长度编码,编码负数效率低下,如果你的字段可能有负值,请改用sint64 long uint32 使用可变长度编码 int uint64 使用可变长度编码 long sint32 使用可变长度编码,符号整型值,这些比常规int32编码负数更有效 int sint64 使用可变长度编码,有符号的int值,这些比常规int64编码负数更有效 long fixed32 总是四字节,如果值经常大于2^28,则比uint32更有效 int fixed64 总是八字节,如果值经常大于2^56,则比uint64更有效 long sfixed32 总是四字节 int sfixed64 总是八字节 long bool null boolean string 字符串必须始终包含UTF8编码或7位ASCII文本 string bytes 可以包含任意字节序列 byteString 如果你使用的是别的语言,那么还有一张对照图,在下面 修饰符 如果一个字段被repeated修饰,则表示它是一个列表类型的字段 如果你现在不确定是否某个值或者变量是否以后会使用的到为了不误写,那么可以保留 message Foo { reserved 2, 15, 9 to 11; reserved "foo", "bar"; string foo = 3 // 编译报错,因为‘foo’已经被标为保留字段 } 缺省值:默认值 string类型的默认值是空字符串 bytes类型的默认值是空字节 bool类型的默认值是false 数字类型的默认值是0 enum类型的默认值是第一个定义的枚举值 message类型(对象,如上文的SearchRequest就是message类型)的默认值与 语言 相关 repeated修饰的字段默认值是空列表,比如repeated string query = 1; = java.util.List<java.lang.String> 如果一个字段的值等于默认值(如bool类型的字段设为false),那么它将不会被序列化,这样的设计是为了节省流量 枚举 之前的都是简单类型,比如int32之类的,那么在message中还可以定义复杂类型 每个枚举值有对应的数值,数值不一定是连续的.第一个枚举值的 数值必须是0且至少有一个枚举值 ,否则编译报错.编译后编译器会为你生成对应语言的枚举类,零值必须为第一个元素,以便与proto 2语义兼容,其中第一个枚举值总是默认的 由于编码原因,出于效率考虑,不推荐使用负数作为枚举值的数值 如下就在之前的基础上添加到了春夏秋冬四个枚举常量值,当然里面的数字标识不用按顺序来 syntax = "proto3"; message SearchRequest { string query = 1; int32 page_number = 2; int32 result_per_page = 3; Groups groups = 4; enum Groups{ SPRING = 0; SUMMER = 1; Autumn = 2; WINTER = 3; } } 如果个别的枚举值的标识是相同的,需要用option allow_alias = true;来声明一下,比如 enum Groups{ option allow_alias = true; SPRING = 0; SUMMER = 2; Autumn = 2; WINTER = 3; } 如果去掉option allow_alias = true;,那么编译会出现错误, 并且注意枚举数常量必须在32位整数的范围内 可以使用messageName.enum的形式,在其他message中引用,比如 message Others { SearchRequest.Groups groups = 1; } 其生成的类中方法定义的返回值就是Mes.SearchRequest.Groups getGroups(); 使用其它的message类型 第一种使用方法如下 syntax = "proto3"; message First { repeated string query = 1; Second second = 2; //引用其他message类型 } message Second { int32 num = 1; } 第二种即import导入使用 syntax = "proto3"; import "second.proto"; //首先导入 message First { repeated string query = 1; Second second = 2; //引用其他message类型 } 上面这种导入如果测试,需要将second.proto移到相同目录下,并且First和Second消息是两个proto文件 嵌套 可以在一个message类型中定义另一个message类型,并且可以一直嵌套下去,类似Java的内部类 message SearchResponse { message Result { string url = 1; string title = 2; repeated string snippets = 3; } repeated Result results = 1; } 可以使用Parent.Type的形式引用嵌套的message message SomeOtherMessage { SearchResponse.Result result = 1; } 更新message 当proto不能满足要求的情况下,那么我们就需要按照要求更新message 不要更改任何已有的字段的数值标识 如果增加新的字段,使用旧格式的字段仍然可以被新产生的代码所解析.相似的,通过新代码产生的消息也可以被旧代码解析:只不过新的字段会被忽视掉.注意:未被识别的字段会在反序列化的过程中丢弃掉,所以如果消息再被传递给新的代码,新的字段依然是不可用的 非required的字段可以移除,只要它们的标识号在新的消息类型中不再使用,但是更好的做法是将此字段重命名,保证其标识号不被其他变量误用 nt32, uint32, int64, uint64,和bool是全部兼容的,如果转换的时候发生不符合的情况,那么会强制类型转换 sint32和sint64是互相兼容的,但是它们与其他整数类型不兼容 string和bytes是兼容的,只要bytes是有效的UTF-8编码 嵌套消息与bytes是兼容的,只要bytes包含该消息的一个编码过的版本 fixed32与sfixed32是兼容的,fixed64与sfixed64是兼容的 枚举类型与int32,uint32,int64和uint64相兼容,,如果转换的时候发生不符合的情况,那么会强制类型转换 Any Any允许包装任意的message类型 ,一个Any类型包括一个可以被序列化bytes类型的任意消息,以及一个URL作为一个全局标识符和解析消息类型.为了使用Any类型,你需要导入import google/protobuf/any.proto import "google/protobuf/any.proto"; message Response { google.protobuf.Any data = 1; } 如上会被解析成private java.util.List<com.google.protobuf.Any> details_; 类型 对于给定的消息类型的默认类型URL是type.googleapis.com/packagename.messagename 不同语言的实现会支持动态库以线程安全的方式去帮助封装或者解封装Any值,例如在java中,Any类型会有特殊的pack()和unpack()访问器 实例 //People.proto syntax = "proto3"; message Get{ string name = 1; } //First.proto syntax = "proto3"; import "google/protobuf/any.proto"; message Response { google.protobuf.Any data = 1; } public class Tests { public static void main(String[] args) throws InvalidProtocolBufferException { People.Get wangziqiang = People.Get.newBuilder().setName("wangziqiang").build(); First.Response build = First.Response.newBuilder().setData(Any.pack(wangziqiang)).build(); System.out.println(build.getData()); System.out.println(build.getData().unpack(People.Get.class).getName()); } /** * 输出结果 * type_url: "type.googleapis.com/Get" * value: "\n\vwangziqiang" * wangziqiang */ } 如上我们就看到了,Any可以包含任何的message,也就是进行pack动作,而unpack是进行解包得到内容,这是我自己的理解 Oneof 用于几个参数只能初始化其中一个的情况,设置其中一个字段会清除其它字段,repeated不可以在其中被使用 syntax = "proto3"; message Name{ oneof set_name{ string first_name = 1; string last_name = 2; } } public class Tests { public static void main(String[] args) { People.Name wangziqiang = People.Name.newBuilder().setFirstName("nn").setLastName("wangziqiang").build(); System.out.println("getFirstName -> " + wangziqiang.getFirstName()); System.out.println("getLastName -> " + wangziqiang.getLastName()); System.out.println(wangziqiang.getFirstName().isEmpty()); System.out.println(wangziqiang.getLastName().isEmpty()); } /** * getFirstName -> wangziqiang * getLastName -> * true * false * 也就可以看出来,只有最后一次设置的字段有值. */ } Map 其实是一种语法糖,如下 message xx{ int32 i = 1; string j = 2; } message ss { xx x = 1; //map<int32,string> = 1 } 需要注意的是key除了floating和bytes的任意标量类型都是可以的,value可以是任意类型 map的顺序不一定 map类型字段不支持repeated 当为.proto文件产生生成文本格式的时候,map会按照key 的顺序排序,数值化的key会按照数值排序 如果有重复的key则后一个key不会被使用 实例 syntax = "proto3"; message Name{ map<int32,string> names = 1; } public class Tests { public static void main(String[] args) throws InvalidProtocolBufferException { People.Name.Builder builder = People.Name.newBuilder(); builder.putNames(1,"one"); builder.putNames(3,"three"); builder.putNames(2,"two"); People.Name name = builder.build(); System.out.println(name); } /** * names { * key: 1 * value: "one" * } * names { * key: 3 * value: "three" * } * names { * key: 2 * value: "two" * } */ } 包 使用方法 syntax = "proto3"; package qidai; message Name{ map<int32,string> names = 1; } 定义服务 如果想要将消息类型用在RPC系统中,可以在.proto文件中定义一个RPC服务接口,protocol buffer编译器将会根据所选择的不同语言生成服务接口代码 syntax = "proto3"; package qidai; message NameRequest{ string name = 1; } message NameResponse{ int32 name_hash = 1; } service rpc_name { rpc getName(NameRequest) returns(NameResponse); } 上面的表示,rpc的参数为NameRequest,而返回值为NameResponse,对于java应用protobuf实现简单的rpc,之后再说 JSON映射 在protobuf中支持使用JSON,那么对于protobuf中的类型和JSON中的类型的对应表如下 proto3 JSON JSON示例 注意 message object {"fBar": v, "g": null, …} 产生JSON对象,消息字段名可以被映射成lowerCamelCase形式,并且成为JSON对象键,null被接受并成为对应字段的默认值 enum string "FOO_BAR" 枚举值的名字在proto文件中被指定 map object {"k": v, …} 所有的键都被转换成string repeated V array [v, …] null被视为空列表 bool true, false true, false string string "Hello World!" bytes base64 string "YWJjMTIzIT8kKiYoKSctPUB+" int32,fixed32,uint32 number 1, -10, 0 JSON值会是一个十进制数,数值型或者string类型都会接受 int64,fixed64,uint64 string "1", "-10" JSON值会是一个十进制数,数值型或者string类型都会接受 float,double number 1.1, -10.0, 0, "NaN", "Infinity" JSON值会是一个数字或者一个指定的字符串如”NaN”,”infinity”或者”-Infinity”,数值型或者字符串都是可接受的,指数符号也可以接受 Any object {"@type": "url", "f": v, … } 如果一个Any保留一个特上述的JSON映射,则它会转换成一个如下形式:{"@type": xxx, "value": yyy}否则,该值会被转换成一个JSON对象,@type字段会被插入所指定的确定的值 Timestamp string "1972-01-01T10:00:20.021Z" 使用RFC 339,其中生成的输出将始终是Z-归一化啊的,并且使用0,3,6或者9位小数 Duration string "1.000340012s", "1s" 生成的输出总是0,3,6或者9位小数,具体依赖于所需要的精度,接受所有可以转换为纳秒级的精度 Struct object { … } 任意的JSON对象,见struct.proto Wrapper types various types 2, "2", "foo", true, "true", null, 0, ... 包装器在JSON中的表示方式类似于基本类型,但是允许nulll,并且在转换的过程中保留null FieldMask string "f.fooBar,h" 见fieldmask.proto ListValue array [foo, bar, …] Value value 任意JSON值 NullValue null JSON null 下面也是对照表 选项 在定义.proto文件时能够标注一系列的options.Options并不改变整个文件声明的含义,但却能够影响特定环境下处理方式.完整的可用选项可以在google/protobuf/descriptor.proto找到,在IDEA中直接搜索descriptor.proto就行 一些选项是文件级别的,意味着它可以作用于最外范围,不包含在任何消息内部、enum或服务定义中.一些选项是消息级别的,意味着它可以用在消息定义的内部.当然有些选项可以作用在域、enum类型、enum值、服务类型及服务方法中 常用的选择 java_package:文件选项,即指定包名,如下,但是自己感觉好像与package com.qidai;一样的作用 syntax = "proto3"; option java_package = "com.qidai"; message NameRequest{ string name = 1; } java_outer_classname:文件选项,即指定外部类类名,如果不指定,那么默认的类名是以proto文件的名字作为类名的 syntax = "proto3"; option java_outer_classname = "Peoples"; //然后编译完成后就是生成的Peoples的类名,而不是以proto文件名作为类名了 message NameRequest{ string name = 1; } optimize_for:文件选项,可以被设置为 SPEED, CODE_SIZE,或者LITE_RUNTIME,这些值将影响代码的生成 SPEED:默认protocol buffer编译器将通过在消息类型上执行序列化、语法分析及其他通用的操作.这种代码是最优的. CODE_SIZE:protocol buffer编译器将会产生最少量的类,通过共享或基于反射的代码来实现序列化、语法分析及各种其它操作.采用该方式产生的代码将比SPEED要少得多, 但是操作要相对慢些,但是生成的代码的访问方式和SPEED是一样的 LITE_RUNTIMEprotocol buffer编译器依赖于运行时核心类库来生成代码(即采用libprotobuf-lite 替代libprotobuf).这种核心类库由于忽略了一 些描述符及反射,要比全类库小得多.这种模式经常在移动手机平台应用多一些 syntax = "proto3"; option optimize_for = SPEED; message NameRequest{ string name = 1; } deprecated:表示废弃了 syntax = "proto3"; message NameRequest{ string name = 1 [deprecated = true]; } 生成访问类 如上我们都是在IDEA插件的帮助下,自动完成编译的,自己接触的是Java,所以在这里列一下Java的生成类的方式,如果你是别的语言,那么请参考给出的参考博文 protoc --proto_path=IMPORT_PATH --java_out=DST_DIR path/to/file.proto IMPORT_PATH声明了一个.proto文件所在的解析import具体目录,如果忽略该值,则使用当前目录,如果有多个目录则可以多次调用--proto_path,-I=IMPORT_PATH是--proto_path的简化形式 --java_out 在目标目录DST_DIR中产生Java代码 作为一个方便的拓展,如果DST_DIR以.zip或者.jar结尾,编译器会将输出写到一个ZIP格式文件或者符合JAR标准的.jar文件中.注意如果输出已经存在则会被覆盖 好了到这就是Protobuf的基本用法,本文参考网上的博文,链接如下,如果本文有什么错误请及时指出 , 谢谢咯 开发学院简书CSDN

优秀的个人博客,低调大师

Lambda入门

Lambda 来源于微积分数学中的 λ,其涵义是声明为了表达一个函数具体需要什么. Table of contents Introduction 使用 Introduction 什么是Lambda? 我们知道,对于一个Java变量,我们可以赋给其一个“值”。 如果你想把“一块代码”赋给一个Java变量,应该怎么做呢? 比如,我想把右边那块代码,赋给一个叫做aBlockOfCode的Java变量: 在Java 8之前,这个是做不到的。但是Java 8问世之后,利用Lambda特性,就可以做到了。 当然,这个并不是一个很简洁的写法。所以,为了使这个赋值操作更加elegant, 我们可以移除一些没用的声明。 这样,我们就成功的非常优雅的把“一块代码”赋给了一个变量。而“这块代码”,或者说“这个被赋给一个变量的函数”,就是一个Lambda表达式。 但是这里仍然有一个问题,就是变量aBlockOfCode的类型应该是什么? 在Java 8里面,所有的Lambda的类型都是一个接口,而Lambda表达式本身,也就是”那段代码“,需要是这个接口的实现。这是我认为理解Lambda的一个关键所在,简而言之就是,Lambda表达式本身就是一个接口的实现。直接这样说可能还是有点让人困扰,我们继续看看例子。我们给上面的aBlockOfCode加上一个类型: 这种只有一个接口函数需要被实现的接口类型,我们叫它”函数式接口“。为了避免后来的人在这个接口中增加接口函数导致其有多个接口函数需要被实现,变成"非函数接口”,我们可以在这个上面加上一个声明@FunctionalInterface, 这样别人就无法在里面添加新的接口函数了: 这样,我们就得到了一个完整的Lambda表达式声明: 使用 Lambda表达式有什么作用? 最直观的作用就是使得代码变得异常简洁。 我们可以对比一下Lambda表达式和传统的Java对同一个接口的实现: 这两种写法本质上是等价的。但是显然,Java 8中的写法更加优雅简洁。并且,由于Lambda可以直接赋值给一个变量,我们就可以直接把Lambda作为参数传给函数, 而传统的Java必须有明确的接口实现的定义,初始化才行: 有些情况下,这个接口实现只需要用到一次。传统的Java 7必须要求你定义一个“污染环境”的接口实现MyInterfaceImpl,而相较之下Java 8的Lambda, 就显得干净很多。 Lambda结合FunctionalInterface Lib, forEach, stream(),method reference等新特性可以使代码变的更加简洁! 直接上例子。 假设Person的定义和List的值都给定。 该注解见lombok 现在需要你打印出guiltyPersons List里面所有LastName以"Z"开头的人的FirstName。 原生态Lambda写法:定义两个函数式接口,定义一个静态函数,调用静态函数并给参数赋值Lambda表达式。 这个代码实际上已经比较简洁了,但是我们还可以更简洁么? 当然可以。在Java 8中有一个函数式接口的包,里面定义了大量可能用到的函数式接口(java.util.function (Java Platform SE 8 ))。所以,我们在这里压根都不需要定义NameChecker和Executor这两个函数式接口,直接用Java 8函数式接口包里的Predicate和Consumer就可以了——因为他们这一对的接口定义和NameChecker/Executor其实是一样的。 第一步简化 - 利用函数式接口包: 静态函数里面的for each循环其实是非常碍眼的。这里可以利用Iterable自带的forEach()来替代。forEach()本身可以接受一个Consumer 参数。 第二步简化 - 用Iterable.forEach()取代foreach loop: 由于静态函数其实只是对List进行了一通操作,这里我们可以甩掉静态函数,直接使用stream()特性来完成。stream()的几个方法都是接受Predicate,Consumer等参数的(java.util.stream (Java Platform SE 8 ))。你理解了上面的内容,stream()这里就非常好理解了,并不需要多做解释。 第三步简化 - 利用stream()替代静态函数: 对比最开始的Lambda写法,这里已经非常非常简洁了。但是如果,我们的要求变一下,变成print这个人的全部信息,及p -> System.out.println(p); 那么还可以利用Method reference来继续简化。所谓Method reference, 就是用已经写好的别的Object/Class的method来代替Lambda expression。格式如下: 第四步简化 - 如果是println(p),则可以利用Method reference代替forEach中的Lambda表达式: 这基本上就是能写的最简洁的版本了 Lambda配合Optional可以使Java对于null的处理变的异常优雅 这里假设我们有一个person object,以及一个person object的Optional wrapper: Optional如果不结合Lambda使用的话,并不能使原来繁琐的null check变的简单。 只有当Optional结合Lambda一起使用的时候,才能发挥出其真正的威力! 我们现在就来对比一下下面四种常见的null处理中,Java 8的Lambda+Optional和传统Java两者之间对于null的处理差异。 情况一 - 存在则开干 情况二 - 存在则返回,无则返回屁 情况三 - 存在则返回,无则由函数产生 情况四 - 夺命连环null检查 由上述四种情况可以清楚地看到,Optional+Lambda可以让我们少写很多ifElse块。尤其是对于情况四那种夺命连环null检查,传统java的写法显得冗长难懂,而新的Optional+Lambda则清新脱俗,清楚简洁 关于Java的Lambda, 还有东西需要讨论和学习。比如如何handle lambda exception,如何利用Lambda的特性来进行parallel processing等。总之,我只是一如既往地介绍个大概,让你大概知道,哦!原来是这样子就OK了。网上关于Lambda有很多相关的教程,多看多练。假以时日,必定有所精益。 转载自知乎用户Sevenvidia,来自Amazon,Lambda 表达式有何用处?如何使用?

优秀的个人博客,低调大师

Druid入门

应用场景 设计一个系统来预估未来一年的广告流量,不是总流量,是任意时间段任何定向(Targeting)条件约束情况下的流量。定向条件有近百种(内容类别,设备平台,用户地域,用户人口属性等),整个时间区间不同组合数(也就是数据行数)是亿级别。目标是秒级的查询响应时间。 一个简单的数据例子如下: 存储系统选择 MySQL不是适合的选择 最容易想到的是用Mysql作为数据存放和查询引擎,由于数据行数太多,Mysql必须通过创建索引或者组合索引来加速查询。 典型的查询包含若干个定向类别,这些定向条件的组合是非常多的(top 80%的查询也会包含几十种组合),故需要创建非常多的组合索引,代价很高。 另外,对于那些没有创建组合索引的查询,查询时间完全不能接受。 为什么没有用Hbase或者Hive Hbase本身是一个经典的基于hdfs的分布式存储系统,通常来说其是行存储的,当创建column families之后,每个column family是列存储的。在这个应用中,可以为每个定向类别(包括日期)创建一个单独的column family,但Hbase本身没有为column family创建bitmap indexing,查询速度应该会受到影响。 另外不用Hbase的一个原因是希望存储系统尽量轻量级,最好不要安装hadoop Hive将查询转化为M/R任务,没法保证查询的快速响应(比如M/R cluster资源竞争很激烈时),而且使用Hive需要以来hadoop cluster,对这个应用来说也略微重量级。 我们需要一个高可用的分布式的列存储系统 核心需求包含2点,一是查询速度快,二是系统的拓展性好,最好是分布式的。 第一点要求意味着最好用column-store而不是row-store,在这个应用中,虽然定向类别有近百种,但是单次查询通常只会涉及几个。对于修改操作较少且查询往往只涉及少数几列的场景使用column-store可以获得快一个量级的查询速度。而且column-store可以通过bitmap indexing,encoding,以及compression来优化查询速度和存储开销 第二点要求一方面是由于我们的数据量较大,并行存储和查询可以减少时间开销,另一方面是数据量每年还在快速上涨,以后可以简单地通过加机器来应对。 对系统的其他要求比较普遍:系统可用性要高,稳定,轻量级,易于上手。 为什么Druid是适合的选择 Druid满足我们上面2点要求,其是一个开源的、分布式的、列存储系统,特别适用于大数据上的(准)实时分析统计。且具有较好的稳定性(Highly Available)。 其相对比较轻量级,文档非常完善,也比较容易上手。 Druid介绍 概念 Segment: Druid中有个重要的数据单位叫segment,其是Druid通过bitmap indexing从raw data生成的(batch or realtime)。 segment保证了查询的速度。可以自己设置每个segment对应的数据粒度,这个应用中广告流量查询的最小粒度是天,所以每天的数据会被创建成一个segment。注意segment是不可修改的,如果需要修改,只能够修改raw data,重新创建segment了 架构 image Druid本身包含5个组成部分:Broker nodes, Historical nodes, Realtime nodes, Coordinator Nodes和indexing services. 分别的作用如下: Broker nodes: 负责响应外部的查询请求,通过查询Zookeeper将请求划分成segments分别转发给Historical和Real-time nodes,最终合并并返回查询结果给外部; Historial nodes: 负责’Historical’ segments的存储和查询。其会从deep storage中load segments,并响应Broder nodes的请求。Historical nodes通常会在本机同步deep storage上的部分segments,所以即使deep storage不可访问了,Historical nodes还是能serve其同步的segments的查询; Real-time nodes: 用于存储和查询热数据,会定期地将数据build成segments移到Historical nodes。一般会使用外部依赖kafka来提高realtime data ingestion的可用性。如果不需要实时ingest数据到cluter中,可以舍弃Real-time nodes,只定时地batch ingestion数据到deep storage; Coordinator nodes: 可以认为是Druid中的master,其通过Zookeeper管理Historical和Real-time nodes,且通过Mysql中的metadata管理Segments Druid中通常还会起一些indexing services用于数据导入,batch data和streaming data都可以通过给indexing services发请求来导入数据。 Druid还包含3个外部依赖 Mysql:存储Druid中的各种metadata(里面的数据都是Druid自身创建和插入的),包含3张表:”druid_config”(通常是空的), “druid_rules”(coordinator nodes使用的一些规则信息,比如哪个segment从哪个node去load)和“druid_segments”(存储每个segment的metadata信息); Deep storage: 存储segments,Druid目前已经支持本地磁盘,NFS挂载磁盘,HDFS,S3等。Deep Storage的数据有2个来源,一个是batch Ingestion, 另一个是real-time nodes; ZooKeeper: 被Druid用于管理当前cluster的状态,比如记录哪些segments从Real-time nodes移到了Historical nodes; 查询 Druid的查询是通过给Broker Nodes发送HTTP POST请求(也可以直接给Historical or Realtime Node),具体可见Druid官方文档。查询条件的描述是json文件,查询的response也是json格式。Druid的查询包含如下4种: Time Boundary Queries: 用于查询全部数据的时间跨度 groupBy Queries: 是Druid的最典型查询方式,非常类似于Mysql的groupBy查询。query body中几个元素可以这么理解: “aggregation”: 对应mysql”select XX from”部分,即你想查哪些列的聚合结果; “dimensions”: 对应mysql”group by XX”,即你想基于哪些列做聚合; “filter”: 对应mysql”where XX”条件,即过滤条件; “granularity”: 数据聚合的粒度; Timeseries queries: 其统计满足filter条件的”rows”上某几列的聚合结果,相比”groupBy Queries”不指定基于哪几列进行聚合,效率更高; TopN queries: 用于查询某一列上按照某种metric排序的最常见的N个values; 本文小结 Druid是一个开源的,分布式的,列存储的,适用于实时数据分析的系统,文档详细,易于上手; Druid在设计时充分考虑到了Highly Available,各种nodes挂掉都不会使得druid停止工作(但是状态会无法更新); Druid中的各个components之间耦合性低,如果不需要streaming data ingestion完全可以忽略realtime node; Druid的数据单位Segment是不可修改的,我们的做法是生成新的segments替换现有的; Druid使用Bitmap indexing加速column-store的查询速度,使用了一个叫做CONCISE的算法来对bitmap indexing进行压缩,使得生成的segments比原始文本文件小很多; 在我们的应用场景下(一共10几台机器,数据大概100列,行数是亿级别),平均查询时间<2秒,是同样机器数目的Mysql cluter的1/100 ~ 1/10; Druid的一些“局限”: Segment的不可修改性简化了Druid的实现,但是如果你有修改数据的需求,必须重新创建segment,而bitmap indexing的过程是比较耗时的; Druid能接受的数据的格式相对简单,比如不能处理嵌套结构的数据

优秀的个人博客,低调大师

Numpy入门

标题中的英文首字母大写比较规范,但在python实际使用中均为小写。 2018年7月23日笔记 0. 学习内容: Python科学计算库:Numpy需要掌握的知识: 1.Numpy简介;2.Numpy程序包;3.简单的Numpy程序;4.为什么使用Numpy; 5.Numpy是什么;6.Numpy数据溢出; 1. Numpy简介 Numpy是python语言中的科学计算库。 下文主要介绍数据科学工具包Numpy的基本用法,内容包括: 1.Numpy的ndarray多维数组创建 2.Numpy的ndarray多维数组索引切片访问 3.Numpy的ndarray多维数组的组合分割 2. Numpy程序包 集成开发环境为Jupyter notebook 语言及其版本为python3.6 安装numpy在cmd中运行命令:pip install numpy,如果电脑安装了最新版的anaconda,则自带jupyter notebook和numpy库。 集成开发环境如下图所示: image_1cj3s95tl1rsg1hlm1fatkhl12bj9.png-24kB 3.简单的Numpy程序 两个一维矩阵做加法 matrix1 = [0,1,4] matrix2 = [0,1,8] matrix3 = [0,2,12] #使用python原有的列表和推导式 matrix1 = [0,1,4] matrix2 = [0,1,8] matrix3 = [a+b for a,b in zip(matrix1,matrix2)] print(matrix3,type(matrix3)) #使用numpy库计算 import numpy matrix1 = numpy.arange(3) ** 2 matrix2 = numpy.arange(3) ** 3 matrix3 = matrix1 + matrix2 print(matrix3,type(matrix3)) 代码及其运行结果如下图所示: image_1cj3sskqr1t6p19dm1so8aodibh13.png-31.5kB 4.为什么使用Numpy 原因有以下3点: 1.对于同样的数值计算任务,由于Numpy能够直接对数组和矩阵进行操作,可以省略很多循环语句使用Numpy要比直接编写Python代码便捷得多; 2.Numpy中数组的存储效率和输入输出性能均远远优于Python中等价的数据结构; 3.Numpy的大部分代码都是用C语言写的,这使得Numpy比纯Python代码高效得多。 5.Numpy是什么 Numpy的全名为Numeric Python,是一个开源的科学计算库,它包括: 1.一个强大的N维数组对象ndarray;2.比较成熟的函数库; 3.用于整合C/C++和Fortran代码的工具包;4.实用的线性代数、傅里叶变换和随机生成函数。 Numpy支持高维度数组计算和矩阵计算,此外也针对了数组提供了大量的科学函数库。 6.Numpy数据溢出 import numpy as np matrix = np.arange(1292) ** 3 print(matrix.dtype) print(matrix[-3:]) print(2 ** 32 - 1291 ** 3) 代码及其运行结果如下图: image_1cj3u1bb713t11jlkjsp1tmjih41t.png-15.8kB 从上面一段代码可以看出:1.np.arange方法产生的一个ndarray对象,对象中的元素默认为int32类型。 2^31 - 1的值为2147483647,如果int32类型超过这个值则算越界,越界后所得值为负数。 越界所得负数的绝对值 + 原本值 = 2 ** 32。 练习 利用Numpy实现两个向量相乘的结果 有两种解答方法:1.利用np.dot方法,需要2个参数,1个参数数据类型为ndarray,长度要相同。 2.利用ndarray对象的dot方法,需要1个参数,参数数据类型为ndarray,长度要相同。 下面代码中有两种解答方法的示例。 import numpy as np matrix1 = np.arange(10) print(matrix1) matrix2 = np.arange(10,20) print(matrix2) print(np.dot(matrix1,matrix2)) print(matrix1.dot(matrix2)) image_1cja6gt6rp7orhr1rq712jff899.png-41.1kB

优秀的个人博客,低调大师

MongoDB入门

2018年7月5日笔记 1.MongoDB简介 1.1 NoSQL数据库是什么 NoSQL,指的是非关系型的数据库。NoSQL有时也称作Not Only SQL的缩写,是对不同的传统的关系型数据库的数据库管理系统的统称。 NoSQL用于超大规模数据的存储。(例如谷歌或Facebook每天为他们的用户收集万亿比特的数据)。这些类型的数据存储不需要固定的模式,无需多余操作就可以横向扩展。 主要解决: 1.对数据库高并发的需求 2.对海量数据的高效率存储和访问的需求 3.对数据库的高扩展性和高可用性的需求 1.2 什么是MongoDB 取自“humongous”(海量),是由10gen团队2007年10月开发并维护的开源,高性能,可扩展,无模式,面向文档(document-oriented)的数据库。其内存储的一种-like结构化数据。它介于关系数据库和非关系数据库之间,是非关系数据库中最像关系数据库的。是一个基于分布式文件存储的数据库。由C++语言编写。旨在为WEB应用提供可扩展的高性能数据存储方案。 官网:http://www.mongodb.com 最新版本:3.6.3(3/20/2018) 支持操作系统:OSX/linux/Windows 1.3 MongoDB数据库的特点 高性能、易部署、易使用、存储数据数据非常方便。主要功能特性有: 1.面向集合存储,易存储对象类型的数据 2.模式自由(没有行,列,关系等概念) 3.支持完全索引,包含内部对象。 4.支持查询 5.支持复制和故障恢复 6.使用高效的二进制数据存储,包括大型对象 7.自动处理碎片,以支持云计算层次的扩展性 8.支持C#、Python、Ruby、Java、C、Javascript、Perl以及C++语言的驱动程序,社区中也提供了对Erlang以及.NET及平台的扩展程序。 9.文件存储格式为BSON(一种JSON的扩展) 10.可通过网络访问 优点:弱一致性、文档结构的存储方式、内置GridFS(分布式文件系统)缺点:不支持事务、空间占用大 1.4 MongoDB数据库的适用场合 适用场景: 1.网站实时数据处理。它非常适合实时的插入、更新与查询,并具备网站实时数据存储所需的复制及高度伸缩性。 2.缓存。由于性能很高,他适合作为信息基础建设的缓存层。在系统重启之后,由它搭建的数据持久化存储层可以避免下层的数据源过载。 3.高伸缩性的场合。非常适合由数十或者数百台服务器组成的数据库,它的路线图中已经包含对MapReduce引擎的内置支持。不适用场景: 1.类似银行或会计系统这种高事务性的系统。 2.传统的商业智能应用 3.复杂的跨文档级联查询 1.5 MongoDB 视觉中国、大众点评、淘宝、优酷、淘宝 2.MongoDB的基本使用 MongoDB的软件安装教程:Windows 平台安装 MongoDB教程 下面的命令能够正确运行的前提是已经启动MongoDB服务 进入MongoDB的Shell.png-26.3kB 2.1 显示已有的数据库 命令:show dbs 图片.png-1kB 2.2 创建库 使用use runoob就已经创建一个名为runoob的数据库,但必须要往runoob数据库中的一个集合中插入一条数据才能用show dbs命令看到runoob数据库,db.mycoll.insert({name:'runoob'})这一句就是往mycoll这个集合中插入一条name字段值为runoob的数据。 在MongoDB中,数据库和集合不需要提前创建,只要使用就自动创建。 命令: use runoob db.mycoll.insert({name:'runoob'}) show dbs 2.3 删除数据库 命令:db.dropDatabase() 图片.png-3.5kB 2.4 创建集合 命令:db.createCollection(name,options) name参数的数据类型是字符串,意义是要创建的集合名称 options参数的数据类型是json格式的文本,意义是制定有关内存大小和索引选项 options可以是如下参数: 图片.png-16.1kB 从下图中可以看出,一开始使用 show collections显示没有集合,创建第一个简单的集合 mycoll,第二个带参数的集合 mycoll2,创建两个集合后,可以用 show collections可以显示这两个集合的存在。 图片.png-4.5kB 2.5 删除集合 命令:db.{}.drop(),大括号中的要填入的内容是要删除的集合的名称。 图片.png-1.6kB 3.MongoDB进阶 3.1 MongoDB常见数据类型 1.String:这是最常见的数据类型来存储数据,在MongoDB中的字符串必须是有效的UTF-8 2.Integer:这种类型是用来存储一个数值。整数可以是32位或64位,这取决与运行软件的机器。 3.Boolean:用来存储布尔值(true/false) 4.Double:用来存储浮点值 5.Min/Max Keys: 用来对BSON元素的最低和最高值比较 6.Arrays:使用此类型的数组或列表存储到一个键 7.Timestamp:时间戳,这可以方便记录的文件已被修改或添加 8.Object:此类型嵌入式的文件中 9.Null:这种类型是用来存储一个Null的 10.Symbol:用于字符串相同,但它通常是保留给特定符号类型的语言使用。 11.Date:用于存储当前时间或日期的Unix时间格式,可以指定自己的日期和时间。 12.ObjectID:用于存储文档的ID 13.Binary data:用于存储二进制数据 14.Regular expression:用于存储正则表达式 3.2 MongoDB插入文档 命令语法:db.{}.insert({}),第一个大括号填入集合名,第二个大括号填入要插入的文档。 图片.png-7.3kB 3.3 MongoDB更新文档 命令语法:db.{}.update({},{}),第一个大括号填入集合名,第二个大括号填入json格式的文本,第三个括号填入两层json格式文本,第一层的第一个元素的键为$set 图片.png 从上图中可以看到集中mycoll中的第一个文档中的第一个元素title的值从MongoDB overview改为了MongoDB。 3.4 MongoDB Save()方法 命令语法: db.{}.save({}),第一个大括号填入集合名,第二个大括号填入json格式的文本 图片.png 3.5 MongoDB删除文档 命令语法: db.{}.remove({},{}),第一个大括号填入集合名,第二个大括号填入json格式的文本,用于选择被删除的文档,成对大括号内可以为空,第三个括号内为非0整数或true时,删除一条满足判断语句的文档。 图片.png 第三个括号内为0或false或者不填时,删除所有满足判断语句的文档,如下图所示。 图片.png 4.MongoDB查询文档 4.1 find()方法 从MongoDB中查询集合数据,需要使用MongoDB的find()方法。 命令语法:db.{}.find({}),第一个大括号填入集合名,第二个大括号填入json格式的文本 如下图所示,find()函数没有参数时,显示这个集合当中的所有文档;find()函数有参数,但是没有找到满足判断条件的语句时,没有打印结果 图片.png-5.4kB 使用pretty()方法可以使在命令行中的数据打印更整齐。 图片.png-6.4kB 4.2 limit()方法 图片.png-10.4kB 作业 创建数据库 school 用来存放一些班级学生信息; use school db.createCollection("students") 你要为学校下的各个年级、班级建立集合;创建集合可以是显式的,也可以是隐式的;在这路你需要显式地创建“一年级一班的”集合grade_1_1; use school db.createCollection("grades") db.createCollection("classes") db.createCollection("grade_1_1") 隐式创建“一年级二班”:grade_1_2创建的同时为“一年级二班”加入一个学生:"name": 'zhangsan', "age": '7', "sex": "0" use school db.grade_1_2.insert({name:"zhangsan",age:7,sex:0}) 因为一些特殊原因,要解散一年级二班,删除一年级二班的信息 use school school.grade_1_2.drop() 使用循环创建一年级的3个班,并随机添加 10 名学生 for(grade_index in (grade = ['grade_1_1', 'grade_1_2', 'grade_1_3'])) { for (var i = 1; i <= 10; i++) { db[grade[grade_index]].insert({ "name": "zhangsan" + i, "sex": Math.round(Math.random() * 10) % 2, "age": Math.round(Math.random() * 6) + 3, "hobby": [] }); } } 6.查看一年级二班grade_1_2中的所有学生 db.grade_1_2.find() 7.查看一年级二班grade_1_2中所有年龄是 4 岁的学生 db.grade_1_2.find({age:4}) 8.查看一年级二班grade_1_2中所有年龄大于 4 岁的学生 db.grade_1_2.find({age:{$gt:4}}) 9.查看一年级二班grade_1_2中所有年龄大于 4 岁并且小于 7 岁的学生 db.grade_1_2.find({age:{gt:4,lt:7}}) 10.查看一年级二班grade_1_2中所有年龄是 4 岁或 6 岁的学生 db.grade_1_2.find({$or:[{age:4},{age:6}]}) 11.查看一年级二班grade_1_2中所有姓名带zhangsan1的学生 db.grade_1_2.find({name:{$regex:"zhangsan1"}}) 12.查看一年级二班grade_1_2中所有姓名带zhangsan1和zhangsan2的学生 db.grade_1_2.find({$or:[{name:"zhangsan1"},{name:"zhangsan2"}]}) 13.查看一年级二班grade_1_2中所有兴趣爱好有三项的学生 db.grade_1_2.find({hobby:{$size:3}}) 14.查看一年级二班grade_1_2中所有兴趣爱好包括画画的学生 db.grade_1_2.find({hobby:"drawing"}) 15.查看一年级二班grade_1_2中所有兴趣爱好既包括画画又包括跳舞的学生 db.grade_1_2.find({hobby:{$all:{"drawing","dance"}}}) 16.查看一年级二班grade_1_2中所有兴趣爱好有三项的学生的 学生数目 db.grade_1_2.find({hobby:{$size:3}}).count() 17.查看一年级二班的第二位学生 db.grade_1_2.find().limit(1).skip(1) 18.查看一年级二班的学生,按年纪升序 db.grade_1_2.find().sort({age:1}) 19.查看一年级二班的学生,按年纪降序 db.grade_1_2.find().sort({age:-1}) 20.查看一年级二班的学生,年龄值有哪些 db.grade_1_2.distinct('age') 21.查看一年级二班的学生,兴趣覆盖范围有哪些 db.grade_1_2.distinct('hobby') 22.查看一年级二班的学生,男生(sex为 0)年龄值有哪些 db.grade_1_2.distinct('age',{'sex':0}) 23.一年级二班grade_1_2, 删除所有 4 岁的学生 db.grade_1_2.remove({age:4}) 24.一年级二班grade_1_2, 删除第一位 6 岁的学生 db.grade_1_2.remove({age:4},1) 25.一年级二班grade_1_2中,修改名为zhangsan7的学生,年龄为 8 岁,兴趣爱好为 跳舞和画画; db.grade_1_2.update({name:"zhangsan7"},{$set:{age:8,hobby:["dance","drawing"]}}) 26.一年级二班grade_1_2中,追加zhangsan7学生兴趣爱好唱歌; db.grade_1_2.update({name:"zhangsan7"},{$push:{hobby:"sing"}}) 27.一年级二班grade_1_2中,追加zhangsan7学生兴趣爱好吹牛和打篮球; db.grade_1_2.update({"name": "zhangsan7"},{$push: {"hobby": {$each: ["brag","basketball"]}}}) 28.一年级二班grade_1_2中,追加zhangsan7学生兴趣爱好唱歌和打篮球,要保证hobby数组不重复; db.grade_1_2.update({"name": "zhangsan7"},{$addToSet: {"hobby": {$each: ["sing","basketball"]}}}) 29.新学年,给一年级二班所有学生的年龄都增加一岁 db.grade_1_2.update({},{$inc:{"age":1}},{multi:true}) 30.一年级二班grade_1_2中,删除zhangsan7学生的sex属性 db.grade_1_2.update({name:"zhangsan7"},{$unset:{sex:1}}) 31.一年级二班grade_1_2中,删除zhangsan7学生的hobby数组中的头元素 db.grade_1_2.update({name:"zhangsan7"},{$pop:{hobby:-1}}) 32.一年级二班grade_1_2中,删除zhangsan7学生的hobby数组中的尾元素 db.grade_1_2.update({name:"zhangsan7"},{$pop:{hobby:1}}) 33.一年级二班grade_1_2中,删除zhangsan7学生的hobby数组中的sing元素 db.grade_1_2.update({name:"zhangsan7"},{$pull:{hobby:"sing"}})

优秀的个人博客,低调大师

入门Webpack

2017年12月7日更新,添加了clean-webpack-plugin,babel-env-preset,添加本文涉及到的所有代码的示例,如果你在学习过程中出错了,可点击此处参考 写在前面的话 阅读本文之前,先看下面这个webpack的配置文件,如果每一项你都懂,那本文能带给你的收获也许就比较有限,你可以快速浏览或直接跳过;如果你和十天前的我一样,对很多选项存在着疑惑,那花一段时间慢慢阅读本文,你的疑惑一定一个一个都会消失;如果你以前没怎么接触过Webpack,而你又你对webpack感兴趣,那么动手跟着本文中那个贯穿始终的例子写一次,写完以后你会发现你已明明白白的走进了Webpack的大门。 // 一个常见的`webpack`配置文件 const webpack = require('webpack'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const ExtractTextPlugin = require('extract-text-webpack-plugin'); module.exports = { entry: __dirname + "/app/main.js", //已多次提及的唯一入口文件 output: { path: __dirname + "/build", filename: "bundle-[hash].js" }, devtool: 'none', devServer: { contentBase: "./public", //本地服务器所加载的页面所在的目录 historyApiFallback: true, //不跳转 inline: true, hot: true }, module: { rules: [{ test: /(\.jsx|\.js)$/, use: { loader: "babel-loader" }, exclude: /node_modules/ }, { test: /\.css$/, use: ExtractTextPlugin.extract({ fallback: "style-loader", use: [{ loader: "css-loader", options: { modules: true, localIdentName: '[name]__[local]--[hash:base64:5]' } }, { loader: "postcss-loader" }], }) } } ] }, plugins: [ new webpack.BannerPlugin('版权所有,翻版必究'), new HtmlWebpackPlugin({ template: __dirname + "/app/index.tmpl.html" //new 一个这个插件的实例,并传入相关的参数 }), new webpack.optimize.OccurrenceOrderPlugin(), new webpack.optimize.UglifyJsPlugin(), new ExtractTextPlugin("style.css") ] }; 什么是WebPack,为什么要使用它? 为什要使用WebPack 现今的很多网页其实可以看做是功能丰富的应用,它们拥有着复杂的JavaScript代码和一大堆依赖包。为了简化开发的复杂度,前端社区涌现出了很多好的实践方法 模块化,让我们可以把复杂的程序细化为小的文件; 类似于TypeScript这种在JavaScript基础上拓展的开发语言:使我们能够实现目前版本的JavaScript不能直接使用的特性,并且之后还能转换为JavaScript文件使浏览器可以识别; Scss,less等CSS预处理器 ... 这些改进确实大大的提高了我们的开发效率,但是利用它们开发的文件往往需要进行额外的处理才能让浏览器识别,而手动处理又是非常繁琐的,这就为WebPack类的工具的出现提供了需求。 什么是Webpack WebPack可以看做是模块打包机:它做的事情是,分析你的项目结构,找到JavaScript模块以及其它的一些浏览器不能直接运行的拓展语言(Scss,TypeScript等),并将其转换和打包为合适的格式供浏览器使用。 WebPack和Grunt以及Gulp相比有什么特性 其实Webpack和另外两个并没有太多的可比性,Gulp/Grunt是一种能够优化前端的开发流程的工具,而WebPack是一种模块化的解决方案,不过Webpack的优点使得Webpack在很多场景下可以替代Gulp/Grunt类的工具。 Grunt和Gulp的工作方式是:在一个配置文件中,指明对某些文件进行类似编译,组合,压缩等任务的具体步骤,工具之后可以自动替你完成这些任务。 Grunt和Gulp的工作流程 Webpack的工作方式是:把你的项目当做一个整体,通过一个给定的主文件(如:index.js),Webpack将从这个文件开始找到你的项目的所有依赖文件,使用loaders处理它们,最后打包为一个(或多个)浏览器可识别的JavaScript文件。 Webpack工作方式 如果实在要把二者进行比较,Webpack的处理速度更快更直接,能打包更多不同类型的文件。 开始使用Webpack 初步了解了Webpack工作方式后,我们一步步的开始学习使用Webpack。 安装 Webpack可以使用npm安装,新建一个空的练习文件夹(此处命名为webpack sample project),在终端中转到该文件夹后执行下述指令就可以完成安装。 //全局安装 npm install -g webpack //安装到你的项目目录 npm install --save-dev webpack 正式使用Webpack前的准备 在上述练习文件夹中创建一个package.json文件,这是一个标准的npm说明文件,里面蕴含了丰富的信息,包括当前项目的依赖模块,自定义的脚本任务等等。在终端中使用npm init命令可以自动创建这个package.json文件 npm init 输入这个命令后,终端会问你一系列诸如项目名称,项目描述,作者等信息,不过不用担心,如果你不准备在npm中发布你的模块,这些问题的答案都不重要,回车默认即可。 package.json文件已经就绪,我们在本项目中安装Webpack作为依赖包 // 安装Webpack npm install --save-dev webpack 回到之前的空文件夹,并在里面创建两个文件夹,app文件夹和public文件夹,app文件夹用来存放原始数据和我们将写的JavaScript模块,public文件夹用来存放之后供浏览器读取的文件(包括使用webpack打包生成的js文件以及一个index.html文件)。接下来我们再创建三个文件: index.html--放在public文件夹中; Greeter.js-- 放在app文件夹中; main.js-- 放在app文件夹中; 此时项目结构如下图所示 项目结构 我们在index.html文件中写入最基础的html代码,它在这里目的在于引入打包后的js文件(这里我们先把之后打包后的js文件命名为bundle.js,之后我们还会详细讲述)。 <!-- index.html --> <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>Webpack Sample Project</title> </head> <body> <div id='root'> </div> <script src="bundle.js"></script> </body> </html> 我们在 Greeter.js中定义一个返回包含问候信息的 html元素的函数,并依据CommonJS规范导出这个函数为一个模块: // Greeter.js module.exports = function() { var greet = document.createElement('div'); greet.textContent = "Hi there and greetings!"; return greet; }; main.js文件中我们写入下述代码,用以把 Greeter模块返回的节点插入页面。 //main.js const greeter = require('./Greeter.js'); document.querySelector("#root").appendChild(greeter()); 正式使用Webpack webpack可以在终端中使用,在基本的使用方法如下: # {extry file}出填写入口文件的路径,本文中就是上述main.js的路径, # {destination for bundled file}处填写打包文件的存放路径 # 填写路径的时候不用添加{} webpack {entry file} {destination for bundled file}指定入口文件后,webpack将自动识别项目所依赖的其它文件,不过需要注意的是如果你的webpack不是全局安装的,那么当你在终端中使用此命令时,需要额外指定其在node_modules中的地址,继续上面的例子,在终端中输入如下命令 # webpack非全局安装的情况 node_modules/.bin/webpack app/main.js public/bundle.js 结果如下 使用命令行打包 可以看出webpack同时编译了main.js和Greeter,js,现在打开index.html,可以看到如下结果 htmlResult1 有没有很激动,已经成功的使用Webpack打包了一个文件了。不过在终端中进行复杂的操作,其实是不太方便且容易出错的,接下来看看Webpack的另一种更常见的使用方法。 通过配置文件来使用Webpack Webpack拥有很多其它的比较高级的功能(比如说本文后面会介绍的loaders和plugins),这些功能其实都可以通过命令行模式实现,但是正如前面提到的,这样不太方便且容易出错的,更好的办法是定义一个配置文件,这个配置文件其实也是一个简单的JavaScript模块,我们可以把所有的与打包相关的信息放在里面。 继续上面的例子来说明如何写这个配置文件,在当前练习文件夹的根目录下新建一个名为webpack.config.js的文件,我们在其中写入如下所示的简单配置代码,目前的配置主要涉及到的内容是入口文件路径和打包后文件的存放路径。 module.exports = { entry: __dirname + "/app/main.js",//已多次提及的唯一入口文件 output: { path: __dirname + "/public",//打包后的文件存放的地方 filename: "bundle.js"//打包后输出文件的文件名 } } 注:“__dirname”是node.js中的一个全局变量,它指向当前执行脚本所在的目录。 有了这个配置之后,再打包文件,只需在终端里运行webpack(非全局安装需使用node_modules/.bin/webpack)命令就可以了,这条命令会自动引用webpack.config.js文件中的配置选项,示例如下: 配合配置文件进行打包 又学会了一种使用Webpack的方法,这种方法不用管那烦人的命令行参数,有没有感觉很爽。如果我们可以连webpack(非全局安装需使用node_modules/.bin/webpack)这条命令都可以不用,那种感觉会不会更爽~,继续看下文。 更快捷的执行打包任务 在命令行中输入命令需要代码类似于node_modules/.bin/webpack这样的路径其实是比较烦人的,不过值得庆幸的是npm可以引导任务执行,对npm进行配置后可以在命令行中使用简单的npm start命令来替代上面略微繁琐的命令。在package.json中对scripts对象进行相关设置即可,设置方法如下。 { "name": "webpack-sample-project", "version": "1.0.0", "description": "Sample webpack project", "scripts": { "start": "webpack" // 修改的是这里,JSON文件不支持注释,引用时请清除 }, "author": "zhang", "license": "ISC", "devDependencies": { "webpack": "3.10.0" } } 注:package.json中的script会安装一定顺序寻找命令对应位置,本地的node_modules/.bin路径就在这个寻找清单中,所以无论是全局还是局部安装的Webpack,你都不需要写前面那指明详细的路径了。 npm的start命令是一个特殊的脚本名称,其特殊性表现在,在命令行中使用npm start就可以执行其对于的命令,如果对应的此脚本名称不是start,想要在命令行中运行时,需要这样用npm run {script name}如npm run build,我们在命令行中输入npm start试试,输出结果如下: 使用npm start 打包代码 现在只需要使用npm start就可以打包文件了,有没有觉得webpack也不过如此嘛,不过不要太小瞧webpack,要充分发挥其强大的功能我们需要修改配置文件的其它选项,一项项来看。 Webpack的强大功能 生成Source Maps(使调试更容易) 开发总是离不开调试,方便的调试能极大的提高开发效率,不过有时候通过打包后的文件,你是不容易找到出错了的地方,对应的你写的代码的位置的,Source Maps就是来帮我们解决这个问题的。 通过简单的配置,webpack就可以在打包时为我们生成的source maps,这为我们提供了一种对应编译文件和源文件的方法,使得编译后的代码可读性更高,也更容易调试。 在webpack的配置文件中配置source maps,需要配置devtool,它有以下四种不同的配置选项,各具优缺点,描述如下: devtool选项 配置结果 source-map 在一个单独的文件中产生一个完整且功能完全的文件。这个文件具有最好的source map,但是它会减慢打包速度; cheap-module-source-map 在一个单独的文件中生成一个不带列映射的map,不带列映射提高了打包速度,但是也使得浏览器开发者工具只能对应到具体的行,不能对应到具体的列(符号),会对调试造成不便; eval-source-map 使用eval打包源文件模块,在同一个文件中生成干净的完整的source map。这个选项可以在不影响构建速度的前提下生成完整的sourcemap,但是对打包后输出的JS文件的执行具有性能和安全的隐患。在开发阶段这是一个非常好的选项,在生产阶段则一定不要启用这个选项; cheap-module-eval-source-map 这是在打包文件时最快的生成source map的方法,生成的Source Map会和打包后的JavaScript文件同行显示,没有列映射,和eval-source-map选项具有相似的缺点; 正如上表所述,上述选项由上到下打包速度越来越快,不过同时也具有越来越多的负面作用,较快的打包速度的后果就是对打包后的文件的的执行有一定影响。 对小到中型的项目中,eval-source-map是一个很好的选项,再次强调你只应该开发阶段使用它,我们继续对上文新建的webpack.config.js,进行如下配置: module.exports = { devtool: 'eval-source-map', entry: __dirname + "/app/main.js", output: { path: __dirname + "/public", filename: "bundle.js" } } cheap-module-eval-source-map方法构建速度更快,但是不利于调试,推荐在大型项目考虑时间成本时使用。 使用webpack构建本地服务器 想不想让你的浏览器监听你的代码的修改,并自动刷新显示修改后的结果,其实Webpack提供一个可选的本地开发服务器,这个本地服务器基于node.js构建,可以实现你想要的这些功能,不过它是一个单独的组件,在webpack中进行配置之前需要单独安装它作为项目依赖 npm install --save-dev webpack-dev-server devserver作为webpack配置选项中的一项,以下是它的一些配置选项,更多配置可参考这里 devserver的配置选项 功能描述 contentBase 默认webpack-dev-server会为根文件夹提供本地服务器,如果想为另外一个目录下的文件提供本地服务器,应该在这里设置其所在目录(本例设置到“public"目录) port 设置默认监听端口,如果省略,默认为”8080“ inline 设置为true,当源文件改变时会自动刷新页面 historyApiFallback 在开发单页应用时非常有用,它依赖于HTML5 history API,如果设置为true,所有的跳转将指向index.html 把这些命令加到webpack的配置文件中,现在的配置文件webpack.config.js如下所示 module.exports = { devtool: 'eval-source-map', entry: __dirname + "/app/main.js", output: { path: __dirname + "/public", filename: "bundle.js" }, devServer: { contentBase: "./public",//本地服务器所加载的页面所在的目录 historyApiFallback: true,//不跳转 inline: true//实时刷新 } } 在 package.json中的 scripts对象中添加如下命令,用以开启本地服务器: "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "start": "webpack", "server": "webpack-dev-server --open" }, 在终端中输入 npm run server即可在本地的 8080端口查看结果 开启本地服务器 Loaders 鼎鼎大名的Loaders登场了! Loaders是webpack提供的最激动人心的功能之一了。通过使用不同的loader,webpack有能力调用外部的脚本或工具,实现对不同格式的文件的处理,比如说分析转换scss为css,或者把下一代的JS文件(ES6,ES7)转换为现代浏览器兼容的JS文件,对React的开发而言,合适的Loaders可以把React的中用到的JSX文件转换为JS文件。 Loaders需要单独安装并且需要在webpack.config.js中的modules关键字下进行配置,Loaders的配置包括以下几方面: test:一个用以匹配loaders所处理文件的拓展名的正则表达式(必须) loader:loader的名称(必须) include/exclude:手动添加必须处理的文件(文件夹)或屏蔽不需要处理的文件(文件夹)(可选); query:为loaders提供额外的设置选项(可选) 不过在配置loader之前,我们把Greeter.js里的问候消息放在一个单独的JSON文件里,并通过合适的配置使Greeter.js可以读取该JSON文件的值,各文件修改后的代码如下: 在app文件夹中创建带有问候信息的JSON文件(命名为config.json) { "greetText": "Hi there and greetings from JSON!" } 更新后的Greeter.js var config = require('./config.json'); module.exports = function() { var greet = document.createElement('div'); greet.textContent = config.greetText; return greet; }; 注由于webpack3.*/webpack2.*已经内置可处理JSON文件,这里我们无需再添加webpack1.*需要的json-loader。在看如何具体使用loader之前我们先看看Babel是什么? Babel Babel其实是一个编译JavaScript的平台,它可以编译代码帮你达到以下目的: 让你能使用最新的JavaScript代码(ES6,ES7...),而不用管新标准是否被当前使用的浏览器完全支持; 让你能使用基于JavaScript进行了拓展的语言,比如React的JSX; Babel的安装与配置 Babel其实是几个模块化的包,其核心功能位于称为babel-core的npm包中,webpack可以把其不同的包整合在一起使用,对于每一个你需要的功能或拓展,你都需要安装单独的包(用得最多的是解析Es6的babel-env-preset包和解析JSX的babel-preset-react包)。 我们先来一次性安装这些依赖包 // npm一次性安装多个依赖模块,模块之间用空格隔开 npm install --save-dev babel-core babel-loader babel-preset-env babel-preset-react 在 webpack中配置Babel的方法如下: module.exports = { entry: __dirname + "/app/main.js",//已多次提及的唯一入口文件 output: { path: __dirname + "/public",//打包后的文件存放的地方 filename: "bundle.js"//打包后输出文件的文件名 }, devtool: 'eval-source-map', devServer: { contentBase: "./public",//本地服务器所加载的页面所在的目录 historyApiFallback: true,//不跳转 inline: true//实时刷新 }, module: { rules: [ { test: /(\.jsx|\.js)$/, use: { loader: "babel-loader", options: { presets: [ "env", "react" ] } }, exclude: /node_modules/ } ] } }; 现在你的webpack的配置已经允许你使用ES6以及JSX的语法了。继续用上面的例子进行测试,不过这次我们会使用React,记得先安装 React 和 React-DOM npm install --save react react-dom 接下来我们使用ES6的语法,更新 Greeter.js并返回一个React组件 //Greeter,js import React, {Component} from 'react' import config from './config.json'; class Greeter extends Component{ render() { return ( <div> {config.greetText} </div> ); } } export default Greeter 修改 main.js如下,使用ES6的模块定义和渲染Greeter模块 // main.js import React from 'react'; import {render} from 'react-dom'; import Greeter from './Greeter'; render(<Greeter />, document.getElementById('root')); 重新使用npm start打包,如果之前打开的本地服务器没有关闭,你应该可以在localhost:8080下看到与之前一样的内容,这说明react和es6被正常打包了。 localhost:8080 Babel的配置 Babel其实可以完全在webpack.config.js中进行配置,但是考虑到babel具有非常多的配置选项,在单一的webpack.config.js文件中进行配置往往使得这个文件显得太复杂,因此一些开发者支持把babel的配置选项放在一个单独的名为 ".babelrc" 的配置文件中。我们现在的babel的配置并不算复杂,不过之后我们会再加一些东西,因此现在我们就提取出相关部分,分两个配置文件进行配置(webpack会自动调用.babelrc里的babel配置选项),如下: module.exports = { entry: __dirname + "/app/main.js",//已多次提及的唯一入口文件 output: { path: __dirname + "/public",//打包后的文件存放的地方 filename: "bundle.js"//打包后输出文件的文件名 }, devtool: 'eval-source-map', devServer: { contentBase: "./public",//本地服务器所加载的页面所在的目录 historyApiFallback: true,//不跳转 inline: true//实时刷新 }, module: { rules: [ { test: /(\.jsx|\.js)$/, use: { loader: "babel-loader" }, exclude: /node_modules/ } ] } }; //.babelrc { "presets": ["react", "env"] } 到目前为止,我们已经知道了,对于模块,Webpack能提供非常强大的处理功能,那那些是模块呢。 一切皆模块 Webpack有一个不可不说的优点,它把所有的文件都都当做模块处理,JavaScript代码,CSS和fonts以及图片等等通过合适的loader都可以被处理。 CSS webpack提供两个工具处理样式表,css-loader和style-loader,二者处理的任务不同,css-loader使你能够使用类似@import和url(...)的方法实现require()的功能,style-loader将所有的计算后的样式加入页面中,二者组合在一起使你能够把样式表嵌入webpack打包后的JS文件中。 继续上面的例子 //安装 npm install --save-dev style-loader css-loader //使用 module.exports = { ... module: { rules: [ { test: /(\.jsx|\.js)$/, use: { loader: "babel-loader" }, exclude: /node_modules/ }, { test: /\.css$/, use: [ { loader: "style-loader" }, { loader: "css-loader" } ] } ] } }; 请注意这里对同一个文件引入多个loader的方法。 接下来,在app文件夹里创建一个名字为"main.css"的文件,对一些元素设置样式 /* main.css */ html { box-sizing: border-box; -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%; } *, *:before, *:after { box-sizing: inherit; } body { margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; } h1, h2, h3, h4, h5, h6, p, ul { margin: 0; padding: 0; }我们这里例子中用到的 webpack只有单一的入口,其它的模块需要通过 import, require, url等与入口文件建立其关联,为了让webpack能找到”main.css“文件,我们把它导入”main.js “中,如下 //main.js import React from 'react'; import {render} from 'react-dom'; import Greeter from './Greeter'; import './main.css';//使用require导入css文件 render(<Greeter />, document.getElementById('root')); 通常情况下,css会和js打包到同一个文件中,并不会打包为一个单独的css文件,不过通过合适的配置webpack也可以把css打包为单独的文件的。 上面的代码说明webpack是怎么把css当做模块看待的,咱们继续看一个更加真实的css模块实践。 CSS module 在过去的一些年里,JavaScript通过一些新的语言特性,更好的工具以及更好的实践方法(比如说模块化)发展得非常迅速。模块使得开发者把复杂的代码转化为小的,干净的,依赖声明明确的单元,配合优化工具,依赖管理和加载管理可以自动完成。 不过前端的另外一部分,CSS发展就相对慢一些,大多的样式表却依旧巨大且充满了全局类名,维护和修改都非常困难。 被称为CSS modules的技术意在把JS的模块化思想带入CSS中来,通过CSS模块,所有的类名,动画名默认都只作用于当前模块。Webpack对CSS模块化提供了非常好的支持,只需要在CSS loader中进行简单配置即可,然后就可以直接把CSS的类名传递到组件的代码中,这样做有效避免了全局污染。具体的代码如下 module.exports = { ... module: { rules: [ { test: /(\.jsx|\.js)$/, use: { loader: "babel-loader" }, exclude: /node_modules/ }, { test: /\.css$/, use: [ { loader: "style-loader" }, { loader: "css-loader", options: { modules: true, // 指定启用css modules localIdentName: '[name]__[local]--[hash:base64:5]' // 指定css的类名格式 } } ] } ] } }; 我们在app文件夹下创建一个 Greeter.css文件来进行一下测试 /* Greeter.css */ .root { background-color: #eee; padding: 10px; border: 3px solid #ccc; } 导入 .root到Greeter.js中 import React, {Component} from 'react'; import config from './config.json'; import styles from './Greeter.css';//导入 class Greeter extends Component{ render() { return ( <div className={styles.root}> //使用cssModule添加类名的方法 {config.greetText} </div> ); } } export default Greeter 放心使用把,相同的类名也不会造成不同组件之间的污染。 应用了css module后的样式 CSS modules 也是一个很大的主题,有兴趣的话可以去其官方文档了解更多。 CSS预处理器 Sass和Less之类的预处理器是对原生CSS的拓展,它们允许你使用类似于variables,nesting,mixins,inheritance等不存在于CSS中的特性来写CSS,CSS预处理器可以这些特殊类型的语句转化为浏览器可识别的CSS语句, 你现在可能都已经熟悉了,在webpack里使用相关loaders进行配置就可以使用了,以下是常用的CSS 处理loaders: Less Loader Sass Loader Stylus Loader 不过其实也存在一个CSS的处理平台-PostCSS,它可以帮助你的CSS实现更多的功能,在其官方文档可了解更多相关知识。 举例来说如何使用PostCSS,我们使用PostCSS来为CSS代码自动添加适应不同浏览器的CSS前缀。 首先安装postcss-loader和autoprefixer(自动添加前缀的插件) npm install --save-dev postcss-loader autoprefixer 接下来,在webpack配置文件中添加 postcss-loader,在根目录新建 postcss.config.js,并添加如下代码之后,重新使用 npm start打包时,你写的css会自动根据Can i use里的数据添加不同前缀了。 //webpack.config.js module.exports = { ... module: { rules: [ { test: /(\.jsx|\.js)$/, use: { loader: "babel-loader" }, exclude: /node_modules/ }, { test: /\.css$/, use: [ { loader: "style-loader" }, { loader: "css-loader", options: { modules: true } }, { loader: "postcss-loader" } ] } ] } } // postcss.config.js module.exports = { plugins: [ require('autoprefixer') ] } 至此,本文已经谈论了处理JS的Babel和处理CSS的PostCSS的基本用法,它们其实也是两个单独的平台,配合webpack可以很好的发挥它们的作用。接下来介绍Webpack中另一个非常重要的功能-Plugins 插件(Plugins) 插件(Plugins)是用来拓展Webpack功能的,它们会在整个构建过程中生效,执行相关的任务。 Loaders和Plugins常常被弄混,但是他们其实是完全不同的东西,可以这么来说,loaders是在打包构建过程中用来处理源文件的(JSX,Scss,Less..),一次处理一个,插件并不直接操作单个文件,它直接对整个构建过程其作用。 Webpack有很多内置插件,同时也有很多第三方插件,可以让我们完成更加丰富的功能。 使用插件的方法 要使用某个插件,我们需要通过npm安装它,然后要做的就是在webpack配置中的plugins关键字部分添加该插件的一个实例(plugins是一个数组)继续上面的例子,我们添加了一个给打包后代码添加版权声明的插件。 const webpack = require('webpack'); module.exports = { ... module: { rules: [ { test: /(\.jsx|\.js)$/, use: { loader: "babel-loader" }, exclude: /node_modules/ }, { test: /\.css$/, use: [ { loader: "style-loader" }, { loader: "css-loader", options: { modules: true } }, { loader: "postcss-loader" } ] } ] }, plugins: [ new webpack.BannerPlugin('版权所有,翻版必究') ], }; 通过这个插件,打包后的JS文件显示如下 版权所有,翻版必究 这就是webpack插件的基础用法了,下面给大家推荐几个常用的插件 HtmlWebpackPlugin 这个插件的作用是依据一个简单的index.html模板,生成一个自动引用你打包后的JS文件的新index.html。这在每次生成的js文件名称不同时非常有用(比如添加了hash值)。 安装 npm install --save-dev html-webpack-plugin 这个插件自动完成了我们之前手动做的一些事情,在正式使用之前需要对一直以来的项目结构做一些更改: 移除public文件夹,利用此插件,index.html文件会自动生成,此外CSS已经通过前面的操作打包到JS中了。 在app目录下,创建一个index.tmpl.html文件模板,这个模板包含title等必须元素,在编译过程中,插件会依据此模板生成最终的html页面,会自动添加所依赖的 css, js,favicon等文件,index.tmpl.html中的模板源代码如下: <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>Webpack Sample Project</title> </head> <body> <div id='root'> </div> </body> </html> 3.更新 webpack的配置文件,方法同上,新建一个 build文件夹用来存放最终的输出文件 const webpack = require('webpack'); const HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = { entry: __dirname + "/app/main.js",//已多次提及的唯一入口文件 output: { path: __dirname + "/build", filename: "bundle.js" }, devtool: 'eval-source-map', devServer: { contentBase: "./public",//本地服务器所加载的页面所在的目录 historyApiFallback: true,//不跳转 inline: true//实时刷新 }, module: { rules: [ { test: /(\.jsx|\.js)$/, use: { loader: "babel-loader" }, exclude: /node_modules/ }, { test: /\.css$/, use: [ { loader: "style-loader" }, { loader: "css-loader", options: { modules: true } }, { loader: "postcss-loader" } ] } ] }, plugins: [ new webpack.BannerPlugin('版权所有,翻版必究'), new HtmlWebpackPlugin({ template: __dirname + "/app/index.tmpl.html"//new 一个这个插件的实例,并传入相关的参数 }) ], }; 再次执行npm start你会发现,build文件夹下面生成了bundle.js和index.html。 build文件夹 Hot Module Replacement Hot Module Replacement(HMR)也是webpack里很有用的一个插件,它允许你在修改组件代码后,自动刷新实时预览修改后的效果。 在webpack中实现HMR也很简单,只需要做两项配置 在webpack配置文件中添加HMR插件; 在Webpack Dev Server中添加“hot”参数; 不过配置完这些后,JS模块其实还是不能自动热加载的,还需要在你的JS模块中执行一个Webpack提供的API才能实现热加载,虽然这个API不难使用,但是如果是React模块,使用我们已经熟悉的Babel可以更方便的实现功能热加载。 整理下我们的思路,具体实现方法如下 Babel和webpack是独立的工具 二者可以一起工作 二者都可以通过插件拓展功能 HMR是一个webpack插件,它让你能浏览器中实时观察模块修改后的效果,但是如果你想让它工作,需要对模块进行额外的配额; Babel有一个叫做react-transform-hrm的插件,可以在不对React模块进行额外的配置的前提下让HMR正常工作; 还是继续上例来实际看看如何配置 const webpack = require('webpack'); const HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = { entry: __dirname + "/app/main.js",//已多次提及的唯一入口文件 output: { path: __dirname + "/build", filename: "bundle.js" }, devtool: 'eval-source-map', devServer: { contentBase: "./public",//本地服务器所加载的页面所在的目录 historyApiFallback: true,//不跳转 inline: true, hot: true }, module: { rules: [ { test: /(\.jsx|\.js)$/, use: { loader: "babel-loader" }, exclude: /node_modules/ }, { test: /\.css$/, use: [ { loader: "style-loader" }, { loader: "css-loader", options: { modules: true } }, { loader: "postcss-loader" } ] } ] }, plugins: [ new webpack.BannerPlugin('版权所有,翻版必究'), new HtmlWebpackPlugin({ template: __dirname + "/app/index.tmpl.html"//new 一个这个插件的实例,并传入相关的参数 }), new webpack.HotModuleReplacementPlugin()//热加载插件 ], }; 安装 react-transform-hmr npm install --save-dev babel-plugin-react-transform react-transform-hmr 配置Babel // .babelrc { "presets": ["react", "env"], "env": { "development": { "plugins": [["react-transform", { "transforms": [{ "transform": "react-transform-hmr", "imports": ["react"], "locals": ["module"] }] }]] } } } 现在当你使用React时,可以热加载模块了,每次保存就能在浏览器上看到更新内容。 产品阶段的构建 目前为止,我们已经使用webpack构建了一个完整的开发环境。但是在产品阶段,可能还需要对打包的文件进行额外的处理,比如说优化,压缩,缓存以及分离CSS和JS。 对于复杂的项目来说,需要复杂的配置,这时候分解配置文件为多个小的文件可以使得事情井井有条,以上面的例子来说,我们创建一个webpack.production.config.js的文件,在里面加上基本的配置,它和原始的webpack.config.js很像,如下 // webpack.production.config.js const webpack = require('webpack'); const HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = { entry: __dirname + "/app/main.js", //已多次提及的唯一入口文件 output: { path: __dirname + "/build", filename: "bundle.js" }, devtool: 'null', //注意修改了这里,这能大大压缩我们的打包代码 devServer: { contentBase: "./public", //本地服务器所加载的页面所在的目录 historyApiFallback: true, //不跳转 inline: true, hot: true }, module: { rules: [{ test: /(\.jsx|\.js)$/, use: { loader: "babel-loader" }, exclude: /node_modules/ }, { test: /\.css$/, use: ExtractTextPlugin.extract({ fallback: "style-loader", use: [{ loader: "css-loader", options: { modules: true } }, { loader: "postcss-loader" }], }) }] }, plugins: [ new webpack.BannerPlugin('版权所有,翻版必究'), new HtmlWebpackPlugin({ template: __dirname + "/app/index.tmpl.html" //new 一个这个插件的实例,并传入相关的参数 }), new webpack.HotModuleReplacementPlugin() //热加载插件 ], }; //package.json { "name": "test", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "start": "webpack", "server": "webpack-dev-server --open", "build": "NODE_ENV=production webpack --config ./webpack.production.config.js --progress" }, "author": "", "license": "ISC", "devDependencies": { ... }, "dependencies": { "react": "^15.6.1", "react-dom": "^15.6.1" } } 注意:如果是window电脑,build需要配置为"build": "set NODE_ENV=production && webpack --config ./webpack.production.config.js --progress".谢谢评论区简友提醒。 优化插件 webpack提供了一些在发布阶段非常有用的优化插件,它们大多来自于webpack社区,可以通过npm安装,通过以下插件可以完成产品发布阶段所需的功能 OccurenceOrderPlugin:为组件分配ID,通过这个插件webpack可以分析和优先考虑使用最多的模块,并为它们分配最小的ID UglifyJsPlugin:压缩JS代码; ExtractTextPlugin:分离CSS和JS文件 我们继续用例子来看看如何添加它们,OccurenceOrder 和 UglifyJS plugins 都是内置插件,你需要做的只是安装其它非内置插件 npm install --save-dev extract-text-webpack-plugin 在配置文件的plugins后引用它们 // webpack.production.config.js const webpack = require('webpack'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const ExtractTextPlugin = require('extract-text-webpack-plugin'); module.exports = { entry: __dirname + "/app/main.js",//已多次提及的唯一入口文件 output: { path: __dirname + "/build", filename: "bundle.js" }, devtool: 'none', devServer: { contentBase: "./public",//本地服务器所加载的页面所在的目录 historyApiFallback: true,//不跳转 inline: true, hot: true }, module: { rules: [ { test: /(\.jsx|\.js)$/, use: { loader: "babel-loader" }, exclude: /node_modules/ }, { test: /\.css$/, use: [ { loader: "style-loader" }, { loader: "css-loader", options: { modules: true } }, { loader: "postcss-loader" } ] } ] }, plugins: [ new webpack.BannerPlugin('版权所有,翻版必究'), new HtmlWebpackPlugin({ template: __dirname + "/app/index.tmpl.html" }), new webpack.optimize.OccurrenceOrderPlugin(), new webpack.optimize.UglifyJsPlugin(), new ExtractTextPlugin("style.css") ], }; 此时执行npm run build可以看见代码是被压缩后的 压缩后的代码 缓存 缓存无处不在,使用缓存的最好方法是保证你的文件名和文件内容是匹配的(内容改变,名称相应改变) webpack可以把一个哈希值添加到打包的文件名中,使用方法如下,添加特殊的字符串混合体([name], [id] and [hash])到输出文件名前 const webpack = require('webpack'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const ExtractTextPlugin = require('extract-text-webpack-plugin'); module.exports = { .. output: { path: __dirname + "/build", filename: "bundle-[hash].js" }, ... }; 现在用户会有合理的缓存了。 带hash值的js名 去除build文件中的残余文件 添加了hash之后,会导致改变文件内容后重新打包时,文件名不同而内容越来越多,因此这里介绍另外一个很好用的插件clean-webpack-plugin。 安装:cnpm install clean-webpack-plugin --save-dev 使用: 引入clean-webpack-plugin插件后在配置文件的plugins中做相应配置即可: const CleanWebpackPlugin = require("clean-webpack-plugin"); plugins: [ ...// 这里是之前配置的其它各种插件 new CleanWebpackPlugin('build/*.*', { root: __dirname, verbose: true, dry: false }) ] 关于clean-webpack-plugin的详细使用可参考这里 总结 其实这是一年前的文章了,趁周末重新运行和修改了一下,现在所有的代码都可以正常运行,所用webpack基于最新的webpack3.5.3。希望依旧能对你有帮助。 这是一篇好长的文章,谢谢你的耐心,能仔细看到了这里,大概半个月前我第一次自己一步步配置项目所需的Webpack后就一直想写一篇笔记做总结,几次动笔都不能让自己满意,总觉得写不清楚。其实关于Webpack本文讲述得仍不完全,不过相信你看完后已经进入Webpack的大门,能够更好的探索其它的关于Webpack的知识了。 欢迎大家在文后发表自己的观点讨论。 更新说明 2017-12-11更新,修改css module部分代码及示例图片,css module真的非常好用,希望大家都能用上。 2017年9月18日更新,添加了一个使用webpack配置多页应用的demo,可以点击此处查看 2017年8月13日更新,本文依据webpack3.5.3将文章涉及代码完全重写,所有代码都在Mac上正常运行过。希望依旧对你学习webpack有帮助。 2017年8月16号更新: 最近在Gitchat上将发起了一场关于webpack的分享,目的在于一起花最短的时间理解和学会webpack,感兴趣的童鞋可以微信扫描注册哈。 原文发布时间为:2018年01月10日原文作者:AlvinJw 本文来源:开源中国 如需转载请联系原作者

优秀的个人博客,低调大师

spring入门

ioc:https://www.jianshu.com/p/695b2a25a6ff 教学视频:https://www.bilibili.com/video/av14839030/ 在eclipse里面遇到的问题 ① .遇到红色波浪线就把鼠标放在上面,导入jar包用在crtl+shift+M ②.The constructor ClassPathXmlApplicationContext(String) refers to the missing type出现此问题可能是jar包没有导入成功,将jar包再导入一次 ioc底层原理 IOC:控制反转,把对象创建交给spring进行配置 DI:依赖注入,向类里面的属性设置值依赖注入不能单独存在,需在ioc基础上完成操作。 1.png 2.png 四个最基本的jar包 beans core context expression common-logging//日志 xml文件限制 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> </beans> 测试文件 package cn.itcast.ioc; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import cn.itcast.property.PropertyDemo1; public class TestIOC { @Test public void testUser() { ApplicationContext context =new ClassPathXmlApplicationContext("applicationContext.xml"); PropertyDemo1 demo1 = (PropertyDemo1) context.getBean("demo"); demo1.test1(); } } Bean标签常用属性 ①.id 起名称,可以任意命名 不能包含特殊符号 根据id得到配置对象 ②.class属性 创建对象所在类的全路径 ③.name属性 功能和id属性一样,name可以包含特殊字符 ④.scope属性 在测试文件中调用两次函数: 4.png singleton 默认值,单例 运行结果: 3.png prototype 多例 5.png 运行结果: request 创建对象并把对象放到request里面 session 创建对象并把对象放到session里面 globalSession 创建对象并把对象放到globalSession里面 属性注入:(spring里面只支持前两种) 3.png set方法注入 package cn.itcast.property; public class Book { private String bookname; public void setBookname(String bookname) { this.bookname=bookname; } public void demobook() { System.out.println("book...."+bookname); } } 然后在applicationContext.xml文件中使用以下方法给bookname赋值 1.png 有参数构造注入 package cn.itcast.property; public class PropertyDemo1 { private String username; public PropertyDemo1(String username) { this.username=username; } public void test1() { System.out.println("demo1........"+username); } } 然后在applicationContext.xml文件中使用以下方法给username赋值 1.png 注入对象类型属性 通过set方法在UserService.java调用UserDao.java中的add函数 UserService.java package cn.itcast.ioc; public class UserService { private UserDao userDao; public void setUserDao(UserDao userDao) { this.userDao= userDao; } public void add() { System.out.println("Service........"); userDao.add(); } } UserDao.java package cn.itcast.ioc; public class UserDao { public void add() { System.out.println("Dao........"); } } 然后在applicationContext.xml文件中使用以下方法给调用 1.png

资源下载

更多资源
Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册