前端技术
HTML
CSS
Javascript
前端框架和UI库
VUE
ReactJS
AngularJS
JQuery
NodeJS
JSON
Element-UI
Bootstrap
Material UI
服务端和客户端
Java
Python
PHP
Golang
Scala
Kotlin
Groovy
Ruby
Lua
.net
c#
c++
后端WEB和工程框架
SpringBoot
SpringCloud
Struts2
MyBatis
Hibernate
Tornado
Beego
Go-Spring
Go Gin
Go Iris
Dubbo
HessianRPC
Maven
Gradle
数据库
MySQL
Oracle
Mongo
中间件与web容器
Redis
MemCache
Etcd
Cassandra
Kafka
RabbitMQ
RocketMQ
ActiveMQ
Nacos
Consul
Tomcat
Nginx
Netty
大数据技术
Hive
Impala
ClickHouse
DorisDB
Greenplum
PostgreSQL
HBase
Kylin
Hadoop
Apache Pig
ZooKeeper
SeaTunnel
Sqoop
Datax
Flink
Spark
Mahout
数据搜索与日志
ElasticSearch
Apache Lucene
Apache Solr
Kibana
Logstash
数据可视化与OLAP
Apache Atlas
Superset
Saiku
Tesseract
系统与容器
Linux
Shell
Docker
Kubernetes
[切换MySQL数据库的操作方法]的搜索结果
这里是文章列表。热门标签的颜色随机变换,标签颜色没有特殊含义。
点击某个标签可搜索标签相关的文章。
点击某个标签可搜索标签相关的文章。
转载文章
...后端写得到秒杀商品的方法 ①、建实体类vo 用于连表查询,得到商品名字 package com.example.seckill.vo;import com.example.seckill.pojo.SeckillGoods;import lombok.Data;@Datapublic class SeckillGoodsVo extends SeckillGoods {private String goodsName;} ②、在SexkillGoodsMapper.xml文件中定义sql <?xml version="1.0" encoding="UTF-8"?><!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.example.seckill.mapper.SeckillGoodsMapper"><select id="queryAll" resultType="com.example.seckill.vo.SeckillGoodsVo">select sg.,g.goods_namefrom t_seckill_goods sg,t_goods gwhere sg.goods_id = g.gid;</select></mapper> ③、在mapper中定义 package com.example.seckill.mapper;import com.example.seckill.pojo.SeckillGoods;import com.baomidou.mybatisplus.core.mapper.BaseMapper;import com.example.seckill.vo.SeckillGoodsVo;import org.springframework.stereotype.Repository;import java.util.List;/ <p> 秒杀商品信息表 Mapper 接口 </p> @author lv @since 2022-03-19/@Repositorypublic interface SeckillGoodsMapper extends BaseMapper<SeckillGoods> {List<SeckillGoodsVo> queryAll();} ④、service层与controller层 service: ISeckillGoodsService: package com.example.seckill.service;import com.example.seckill.pojo.SeckillGoods;import com.baomidou.mybatisplus.extension.service.IService;import com.example.seckill.util.response.ResponseResult;import com.example.seckill.vo.SeckillGoodsVo;import java.util.List;/ <p> 秒杀商品信息表 服务类 </p> @author lv @since 2022-03-19/public interface ISeckillGoodsService extends IService<SeckillGoods> {ResponseResult<List<SeckillGoodsVo>> queryAll();} SeckillGoodsServiceImpl: package com.example.seckill.service.impl;import com.example.seckill.pojo.SeckillGoods;import com.example.seckill.mapper.SeckillGoodsMapper;import com.example.seckill.service.ISeckillGoodsService;import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;import com.example.seckill.util.response.ResponseResult;import com.example.seckill.vo.SeckillGoodsVo;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import java.util.List;/ <p> 秒杀商品信息表 服务实现类 </p> @author lv @since 2022-03-19/@Servicepublic class SeckillGoodsServiceImpl extends ServiceImpl<SeckillGoodsMapper, SeckillGoods> implements ISeckillGoodsService {@Autowiredprivate SeckillGoodsMapper seckillGoodsMapper;@Overridepublic ResponseResult<List<SeckillGoodsVo>> queryAll() {List<SeckillGoodsVo> list= seckillGoodsMapper.queryAll();return ResponseResult.success(list);} } controller: SeckillGoodsController: package com.example.seckill.controller;import com.example.seckill.service.ISeckillGoodsService;import com.example.seckill.util.response.ResponseResult;import com.example.seckill.vo.SeckillGoodsVo;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;import java.util.List;/ <p> 秒杀商品信息表 前端控制器 </p> @author lv @since 2022-03-19/@RestController@RequestMapping("/seckillGoods")public class SeckillGoodsController {@Autowiredprivate ISeckillGoodsService seckillGoodsService;@RequestMapping("/queryAll")public ResponseResult<List<SeckillGoodsVo>> queryAll(){return seckillGoodsService.queryAll();} } 得到秒杀商品数据: 3、前端显示数据 ①、编辑跳转秒杀界面 goodList.ftl: <!DOCTYPE html><html lang="en"><head><include "../common/head.ftl"><style>.layui-this{background: deepskyblue !important;}</style></head><body class="layui-container layui-bg-orange"><div class="layui-tab"><ul class="layui-tab-title"><li class="layui-this">普通商品</li><li>秒杀商品</li></ul><-- 普通商品--><div class="layui-tab-content"><div class="layui-tab-item layui-show"><div class="layui-form-item"><label class="layui-form-label">搜索栏</label><div class="layui-input-inline"><input type="text" id="normal_name" name="text" placeholder="请输入搜索内容" class="layui-input"></div><div class="layui-input-inline"><button class="layui-btn layui-btn-primary" id="normal_search">🔍</button><button class="layui-btn layui-btn-primary" id="normal_add">增加</button></div></div><table id="normal_goods" lay-filter="normal_goods"></table><script type="text/html" id="button_1"><a class="layui-btn layui-btn-xs" lay-event="normal_del">删除</a><a class="layui-btn layui-btn-xs" lay-event="normal_edit">编辑</a></script></div><--秒杀界面--><div class="layui-tab-item"><div class="layui-form-item"><label class="layui-form-label">搜索栏</label><div class="layui-input-inline"><input type="text" id="seckill_name" name="text" placeholder="请输入搜索内容" class="layui-input"></div><div class="layui-input-inline"><button class="layui-btn layui-btn-primary" id="seckill_search">🔍</button><button class="layui-btn layui-btn-primary" id="seckill_add">增加</button></div></div><table id="seckill_goods" lay-filter="seckill_goods"></table></div></div></div></div><--引入js--><script src="/static/asset/js/project/goodsList.js"></script></body></html> ②、获取数据 goodList.js: // 秒杀商品let seckill_table=table.render({elem: 'seckill_goods',height: 500,url: '/seckillGoods/queryAll' //数据接口,parseData(res){ //res 即为原始返回的数据return {"code": res.code===200?0:1, //解析接口状态"msg": res.message, //解析提示文本"count": res.total, //解析数据长度"data": res.data //解析数据列表};},cols: [[ //表头{field: 'id', title: '秒杀商品编号', width:80, sort: true},{field: 'goodsId', title: '商品名字id'},{field: 'seckillPrice', title: '秒杀价格'},{field: 'stockCount', title: '秒杀库存'},{field: 'startDate', title: '活动开始时间'},{field: 'endDate', title: '活动结束时间'},{field: 'goodsName', title: '商品名称'}]]}); 呈现界面: 二、秒杀商品添加 1、后端:接收前端添加秒杀商品的数据 ①、实体类vo:SeckillGoodsVo private List<Map<String,Object>> goods; 修改实体类时间的类型:SeckillGoods @ApiModelProperty("秒杀开始时间")@TableField("start_date")@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")private Timestamp startDate;@ApiModelProperty("秒杀结束时间")@TableField("end_date")@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")private Timestamp endDate; ②、mapper层:SeckillGoodsMapper int addGoods(SeckillGoodsVo seckillGoodsVo); ③、mapper.xml层:SeckillGoodsMapper 批量插入秒杀商品的sql语句: <insert id="addGoods">insert into t_seckill_goods(goods_id, seckill_price, stock_count, start_date, end_date)values<foreach collection="goods" item="g" separator=",">({g.gid},{g.goodsPrice},{g.goodsStock},{startDate},{endDate})</foreach></insert> ④、service层 ISeckillGoodsService: ResponseResult<List<SeckillGoodsVo>> addGoods(SeckillGoodsVo seckillGoodsVo); SeckillGoodsServiceImpl: @Overridepublic ResponseResult<List<SeckillGoodsVo>> addGoods(SeckillGoodsVo seckillGoodsVo) {int goods=seckillGoodsMapper.addGoods(seckillGoodsVo);return ResponseResult.success(goods);} ⑤、controller层 @RequestMapping("/add")public ResponseResult<List<SeckillGoodsVo>> add(@RequestBody SeckillGoodsVo seckillGoodsVo){return seckillGoodsService.addGoods(seckillGoodsVo);} 2、前端 ①、定义数据与刷新、添加 goodsList.js: var layer,row,seckill_table// 添加秒杀商品$("seckill_add").click(()=>{layer.open({type:2,content: '/goods/SeckillGoodsOperate',area: ['800px','600px']})})// 秒杀商品刷新var seckill_reload = ()=> {seckill_table.reload({page:{curr:1 //current} });} var layer,row,seckill_tablelayui.define(()=>{let table=layui.tablelayer=layui.layerlet $=layui.jquerylet normal_table=table.render({elem: 'normal_goods',height: 500,url: '/goods/queryAll' //数据接口,page: true //开启分页,parseData(res){ //res 即为原始返回的数据return {"code": res.code===200?0:1, //解析接口状态"msg": res.message, //解析提示文本"count": res.total, //解析数据长度"data": res.data //解析数据列表};},//用于对分页请求的参数:page、limit重新设定名称request: {pageName: 'page' //页码的参数名称,默认:page,limitName: 'rows' //每页数据量的参数名,默认:limit},cols: [[ //表头{field: 'gid', title: '商品编号', width:80, sort: true, fixed: 'left'},{field: 'goodsName', title: '商品名字'},{field: 'goodsTitle', title: '商品标题'},{field: 'goodsImg',title: '商品图片',width:200,templet: (goods) => <b onmouseover='showImg("${goods.goodsImg}",this)'> + goods.goodsImg + </b> },{field: 'goodsDetail', title: '商品详情'},{field: 'goodsPrice', title: '商品价格', sort: true},{field: 'goodsStock', title: '商品库存', sort: true},{field: 'operate', title: '商品操作',toolbar: 'button_1'}]]});// 刷新表格let reloadTable=()=>{let goodsName=$("normal_value").val()// 【JS】自动化渲染的重载,重载表格normal_table.reload({where: {//设定异步数据接口的额外参数,height: 300goodsName},page:{curr:1 //current} });}// 搜索$("normal_search").click(reloadTable)// 增加$("normal_add").click(()=>{row = nullopenDialog()})//工具条事件table.on('tool(normal_goods)', function(obj) { //注:tool 是工具条事件名,test 是 table 原始容器的属性 lay-filter="对应的值"let data = obj.data; //获得当前行数据let layEvent = obj.event; //获得 lay-event 对应的值(也可以是表头的 event 参数对应的值)let tr = obj.tr; //获得当前行 tr 的 DOM 对象(如果有的话)if (layEvent === 'normal_del') { //删除row = data//获得当前行的数据let url="/goods/del/"+data.gidlayer.confirm('确定删除吗?',{title:'删除'}, function(index){//向服务端发送删除指令og$.getJSON(url,{gid:data.gid}, function(ret){layer.close(index);//关闭弹窗reloadTable()});layer.close(index);//关闭弹窗});}if (layEvent === 'normal_edit') { //编辑row = dataopenDialog()} })// 页面弹出let openDialog=()=>{// 如果是iframe层layer.open({type: 2,content: '/goods/goodsOperate', //这里content是一个URL,如果你不想让iframe出现滚动条,你还可以content: ['http://sentsin.com', 'no']area:['800px','600px'],btn: ['确定','取消'],yes(index,layero){let url="/goods/insert"// 拿到表格数据let data=$(layero).find("iframe")[0].contentWindow.getFormData()if(row) {url="/goods/edit"}$.ajax({url,data,datatype: "json",success(res){layer.closeAll()reloadTable()layer.msg(res.message)} })} });}// -------------------------秒杀商品-------------------------------------------seckill_table=table.render({elem: 'seckill_goods',height: 500,url: '/seckillGoods/queryAll' //数据接口,parseData(res){ //res 即为原始返回的数据return {"code": res.code===200?0:1, //解析接口状态"msg": res.message, //解析提示文本"count": res.total, //解析数据长度"data": res.data //解析数据列表};},cols: [[ //表头{field: 'id', title: '秒杀商品编号', width:80, sort: true},{field: 'goodsId', title: '商品名字id'},{field: 'seckillPrice', title: '秒杀价格'},{field: 'stockCount', title: '秒杀库存'},{field: 'startDate', title: '活动开始时间'},{field: 'endDate', title: '活动结束时间'},{field: 'goodsName', title: '商品名称'}]]});// 添加秒杀商品$("seckill_add").click(()=>{layer.open({type:2,content: '/goods/SeckillGoodsOperate',area: ['800px','600px']})})})// 图片显示let showImg = (src,obj)=> {layer.tips(<img src="${src}" width="100px">, obj);}// 秒杀商品刷新var seckill_reload = ()=> {seckill_table.reload({page:{curr:1 //current} });} ②、增加秒杀商品弹出页面样式 <!DOCTYPE html><html lang="en"><head><meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"><link rel="stylesheet" href="/static/asset/js/layui/css/layui.css" media="all"></head><body><div style="padding:15px 0px;"><div class="layui-condition"><form id="fm" name="fm" action="/" method="post" class="layui-form"><div class="layui-form-item"><div class="layui-inline"><label class="layui-form-label" style="width: 100px;text-align: left;">秒杀活动时间:</label><div class="layui-input-inline" style="width:280px;"><input type="text" class="layui-input" id="dt"></div><div class="layui-input-inline"><button class="layui-btn" id="btn_save" type="button"><i class="fa fa-search fa-right"></i>保 存</button></div></div></div></form></div><div class="layui-fluid" style="margin-top:-18px;"><table id="tb_goods" class="layui-table" lay-filter="tb_goods" style="margin-top:-5px;"></table></div></div><script src="/static/asset/js/layui/layui.js"></script><script src="/static/asset/js/project/seckillGoodsOperate.js"></script></body></html> ③、实现增加秒杀商品 seckillGoodsOperate.js: layui.define(()=>{let table=layui.tablelet laydate = layui.laydatelet $=layui.jquerylet layer=layui.layer// 读取普通商品table.render({elem: 'tb_goods',height: 500,url: '/goods/queryAll' //数据接口,page: true //开启分页,parseData(res){ //res 即为原始返回的数据return {"code": res.code===200?0:1, //解析接口状态"msg": res.message, //解析提示文本"count": res.total, //解析数据长度"data": res.data //解析数据列表};},//用于对分页请求的参数:page、limit重新设定名称request: {pageName: 'page' //页码的参数名称,默认:page,limitName: 'rows' //每页数据量的参数名,默认:limit},cols: [[ //表头// 全选按钮{field: '', type:"checkbox"},{field: 'gid', title: '商品编号', width:80},{field: 'goodsName', title: '商品名字'},{field: 'goodsTitle', title: '商品标题'},{field: 'goodsDetail', title: '商品详情'},{field: 'goodsPrice', title: '商品价格', sort: true},{field: 'goodsStock', title: '商品库存', sort: true}]]});// 构建时间选择器//执行一个laydate实例laydate.render({elem: 'dt', //指定元素type: "datetime",range: "~"});$("btn_save").click(()=>{// 获取时间let val=$("dt").val()if(!val){layer.msg("请选择时间")return}// 解析时间2022-2-2 ~2022-5-2let startDate=new Date(val.split("~")[0]).getTime()let endDate=new Date(val.split("~")[1]).getTime()// 获得选中的普通商品,获取选中行的数据let rows= table.checkStatus('tb_goods').data; //idTest 即为基础参数 id 对应的值if(!rows||rows.length===0){layer.msg("请选择数据")return}layer.prompt(function(value, index, elem){// 修改每个商品的数量rows.forEach(e=>{e.goodsStock=value})let data={startDate,endDate,goods:rows}// 访问后台的秒杀商品的接口$.ajax({url: "/seckillGoods/add",contentType:'application/json',data: JSON.stringify(data),datatype:"json",//返回类型type:"post",success(res){parent.seckill_reload()layer.closeAll()parent.layer.closeAll()layer.msg(res.message)} })});})}) ④、展示结果 增加成功: 三、秒杀商品的操作 1、后端操作秒杀单个商品详情 ①、mapper层 SeckillGoodsMapper: Map<String,Object> querySeckillGoodsById(Long id); mapper.xml文件:SeckillGoodsMapper.xml <select id="querySeckillGoodsById" resultType="map">select sg.id,sg.goods_id,sg.seckill_price,sg.stock_count,sg.start_date,sg.end_date,g.goods_img,g.goods_title,g.goods_detail,g.goods_name,(casewhen current_timestamp < sg.start_date then 0when (current_timestamp between sg.start_date and sg.end_date) then 1when current_timestamp > sg.end_date then 2end) goods_statusfrom t_goods g,t_seckill_goods sgwhere g.gid = sg.goods_idand sg.id = {0}</select> ②、service层 ISeckillGoodsService: Map<String,Object> querySeckillGoodsById(Long id); SeckillGoodsServiceImpl: @Overridepublic Map<String, Object> querySeckillGoodsById(Long id) {return seckillGoodsMapper.querySeckillGoodsById(id);} ③、controller层:SeckillGoodsController package com.example.seckill.controller;import com.example.seckill.service.ISeckillGoodsService;import com.example.seckill.util.response.ResponseResult;import com.example.seckill.vo.SeckillGoodsVo;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.;import org.springframework.web.servlet.ModelAndView;import java.util.List;/ <p> 秒杀商品信息表 前端控制器 </p> @author lv @since 2022-03-19/@Controller@RequestMapping("/seckillGoods")public class SeckillGoodsController {@Autowiredprivate ISeckillGoodsService seckillGoodsService;// 返回json@ResponseBody@RequestMapping("/queryAll")public ResponseResult<List<SeckillGoodsVo>> queryAll(){return seckillGoodsService.queryAll();}@ResponseBody@RequestMapping("/add")public ResponseResult<List<SeckillGoodsVo>> add(@RequestBody SeckillGoodsVo seckillGoodsVo){return seckillGoodsService.addGoods(seckillGoodsVo);}// 正常跳转界面@RequestMapping("/query/{id}")public ModelAndView querySeckillGoodsById(@PathVariable("id") Long id) {ModelAndView mv = new ModelAndView("/goods/goodsSeckill");mv.addObject("goods", seckillGoodsService.querySeckillGoodsById(id));return mv;} } 2、前端展示 ①、在goodsList.js增加列的操作 {field: '', title: '操作', width: 140,templet: function (d) {return <div><a class="layui-btn layui-btn-xs layui-btn-danger">删除</a><a href="/seckillGoods/query/${d.id}" class="layui-btn layui-btn-xs layui-btn-normal">秒杀</a></div>;} } ②、添加秒杀详情界面 :goodsSkill.ftl <!DOCTYPE html><html lang="en"><head><include "../common/head.ftl"/></head><body><table style="position: absolute;top:-10px;" class="layui-table" border="1" cellpadding="0" cellspacing="0"><tr><td style="width:120px;">商品图片</td><td><img src="${goods['goods_img']}" alt=""></td></tr><tr><td>商品名称</td><td>${goods['goods_name']}</td></tr><tr><td>商品标题</td><td>${goods['goods_title']}</td></tr><tr><td>商品价格</td><td>${goods['seckill_price']}</td></tr><tr><td>开始时间</td><td><div style="position: relative;${(goods['goods_status']==1)?string('top:10px;','')}">${goods['start_date']?string("yyyy-MM-dd HH:mm:ss")}-${goods['end_date']?string("yyyy-MM-dd HH:mm:ss")}<if goods['goods_status']==0>活动未开始<elseif goods['goods_status']==1>活动热卖中<div style="position:relative;top:-10px;float:right;"><input type="hidden" id="goodsId" value="${goods['goods_id']}" name="goodsId"/><button class="layui-btn" id="buy">立即抢购</button></div><else>活动已结束</if></div></td></tr></table><script src="/static/asset/js/project/goodsSeckill.js"></script></body></html> ③、实现:goodsSkill.js let layer, form, $;layui.define(() => {layer = layui.layerform = layui.form$ = layui.jquery$('buy').click(() => {$.ajax({url: '/seckillOrder/addOrder',data: {goodsId: $('goodsId').val()},dataType: 'json',type: 'post',async: false,success: function (rs) {if (rs.code === 200)layer.msg(rs.message)elselayer.msg(rs.message)} })});}) ④、展示效果 点击秒杀: 3、后端操作秒杀抢购功能 ①、导入雪花id工具包:SnowFlake package com.example.seckill.util;@SuppressWarnings("all")public class SnowFlake {/ 起始的时间戳/private final static long START_STMP = 1480166465631L;/ 每一部分占用的位数/private final static long SEQUENCE_BIT = 12; //序列号占用的位数private final static long MACHINE_BIT = 5; //机器标识占用的位数private final static long DATACENTER_BIT = 5;//数据中心占用的位数/ 每一部分的最大值/private final static long MAX_DATACENTER_NUM = -1L ^ (-1L << DATACENTER_BIT);private final static long MAX_MACHINE_NUM = -1L ^ (-1L << MACHINE_BIT);private final static long MAX_SEQUENCE = -1L ^ (-1L << SEQUENCE_BIT);/ 每一部分向左的位移/private final static long MACHINE_LEFT = SEQUENCE_BIT;private final static long DATACENTER_LEFT = SEQUENCE_BIT + MACHINE_BIT;private final static long TIMESTMP_LEFT = DATACENTER_LEFT + DATACENTER_BIT;private long datacenterId; //数据中心private long machineId; //机器标识private long sequence = 0L; //序列号private long lastStmp = -1L;//上一次时间戳public SnowFlake(long datacenterId, long machineId) {if (datacenterId > MAX_DATACENTER_NUM || datacenterId < 0) {throw new IllegalArgumentException("datacenterId can't be greater than MAX_DATACENTER_NUM or less than 0");}if (machineId > MAX_MACHINE_NUM || machineId < 0) {throw new IllegalArgumentException("machineId can't be greater than MAX_MACHINE_NUM or less than 0");}this.datacenterId = datacenterId;this.machineId = machineId;}public static void main(String[] args) {SnowFlake snowFlake = new SnowFlake(2, 3);long start = System.currentTimeMillis();for (int i = 0; i < 1000000; i++) {System.out.println(snowFlake.nextId());}System.out.println(System.currentTimeMillis() - start);}/ 产生下一个ID @return/public synchronized long nextId() {long currStmp = getNewstmp();if (currStmp < lastStmp) {throw new RuntimeException("Clock moved backwards. Refusing to generate id");}if (currStmp == lastStmp) {//相同毫秒内,序列号自增sequence = (sequence + 1) & MAX_SEQUENCE;//同一毫秒的序列数已经达到最大if (sequence == 0L) {currStmp = getNextMill();} } else {//不同毫秒内,序列号置为0sequence = 0L;}lastStmp = currStmp;return (currStmp - START_STMP) << TIMESTMP_LEFT //时间戳部分| datacenterId << DATACENTER_LEFT //数据中心部分| machineId << MACHINE_LEFT //机器标识部分| sequence; //序列号部分}private long getNextMill() {long mill = getNewstmp();while (mill <= lastStmp) {mill = getNewstmp();}return mill;}private long getNewstmp() {return System.currentTimeMillis();} } ②、service层 ISeckillOrderService : package com.example.seckill.service;import com.example.seckill.pojo.SeckillOrder;import com.baomidou.mybatisplus.extension.service.IService;import com.example.seckill.pojo.User;import com.example.seckill.util.response.ResponseResult;/ <p> 秒杀订单信息表 服务类 </p> @author lv @since 2022-03-19/public interface ISeckillOrderService extends IService<SeckillOrder> {ResponseResult<?> addOrder(Long goodsId, User user);} SeckillOrderServiceImpl : package com.example.seckill.service.impl;import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;import com.example.seckill.exception.BusinessException;import com.example.seckill.mapper.GoodsMapper;import com.example.seckill.mapper.OrderMapper;import com.example.seckill.mapper.SeckillGoodsMapper;import com.example.seckill.pojo.;import com.example.seckill.mapper.SeckillOrderMapper;import com.example.seckill.service.ISeckillOrderService;import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;import com.example.seckill.util.SnowFlake;import com.example.seckill.util.response.ResponseResult;import com.example.seckill.util.response.ResponseResultCode;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import org.springframework.transaction.annotation.Transactional;/ <p> 秒杀订单信息表 服务实现类 </p> @author lv @since 2022-03-19/@Servicepublic class SeckillOrderServiceImpl extends ServiceImpl<SeckillOrderMapper, SeckillOrder> implements ISeckillOrderService {@Autowiredprivate SeckillGoodsMapper seckillGoodsMapper;@Autowiredprivate GoodsMapper goodsMapper;@Autowiredprivate OrderMapper orderMapper;@Transactional(rollbackFor = Exception.class)@Overridepublic ResponseResult<?> addOrder(Long goodsId, User user) {// 下单前判断库存数SeckillGoods goods = seckillGoodsMapper.selectOne(new QueryWrapper<SeckillGoods>().eq("goods_id", goodsId));if (goods == null) {throw new BusinessException(ResponseResultCode.SECKILL_ORDER_ERROR);}if (goods.getStockCount() < 1) {throw new BusinessException(ResponseResultCode.SECKILL_ORDER_ERROR);}// 限购SeckillOrder one = this.getOne(new QueryWrapper<SeckillOrder>().eq("user_id", user.getId()).eq("goods_id", goodsId));if (one != null) {throw new BusinessException(ResponseResultCode.SECKILL_ORDER_EXISTS_ERROR);}// 库存减一int i = seckillGoodsMapper.update(null, new UpdateWrapper<SeckillGoods>().eq("goods_id", goodsId).setSql("stock_count=stock_count-1"));// 根据商品编号查询对应的商品(拿名字)Goods goodsInfo = goodsMapper.selectOne(new QueryWrapper<Goods>().eq("gid", goodsId));// 生成订单//生成雪花idSnowFlake snowFlake = new SnowFlake(5, 9);long id = snowFlake.nextId();//生成对应的订单Order normalOrder = new Order();normalOrder.setOid(id);normalOrder.setUserId(user.getId());normalOrder.setGoodsId(goodsId);normalOrder.setGoodsName(goodsInfo.getGoodsName());normalOrder.setGoodsCount(1);normalOrder.setGoodsPrice(goods.getSeckillPrice());orderMapper.insert(normalOrder);//生成秒杀订单SeckillOrder seckillOrder = new SeckillOrder();seckillOrder.setUserId(user.getId());seckillOrder.setOrderId(normalOrder.getOid());seckillOrder.setGoodsId(goodsId);this.save(seckillOrder);return ResponseResult.success();} } ③、controller层 SeckillOrderController : package com.example.seckill.controller;import com.example.seckill.pojo.User;import com.example.seckill.service.ISeckillOrderService;import com.example.seckill.util.response.ResponseResult;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;/ <p> 秒杀订单信息表 前端控制器 </p> @author lv @since 2022-03-19/@RestController@RequestMapping("/seckillOrder")public class SeckillOrderController {@Autowiredprivate ISeckillOrderService seckillOrderService;@RequestMapping("/addOrder")public ResponseResult<?> addOrder(Long goodsId, User user){return seckillOrderService.addOrder(goodsId,user);} } ④、呈现结果 限购次数: 本期内容结束,下期内容更完善!!!!!!!!!!!!!!!!!!!!!1 本篇文章为转载内容。原文链接:https://blog.csdn.net/weixin_60389087/article/details/123601288。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-02-25 23:20:34
121
转载
转载文章
...tice公司及其收购数据库技术公司–StormDB的产品。Postgres-XL是一个横向扩展的开源数据库集群,具有足够的灵活性来处理不同的数据库任务。 Postgres-XL功能特性 开放源代码:(源协议使用宽松的“Mozilla Public License”许可,允许将开源代码与闭源代码混在一起使用。) 完全的ACID支持 可横向扩展的关系型数据库(RDBMS) 支持OLAP应用,采用MPP(Massively Parallel Processing:大规模并行处理系统)架构模式 支持OLTP应用,读写性能可扩展 集群级别的ACID特性 多租户安全 也可被用作分布式Key-Value存储 事务处理与数据分析处理混合型数据库 支持丰富的SQL语句类型,比如:关联子查询 支持绝大部分PostgreSQL的SQL语句 分布式多版本并发控制(MVCC:Multi-version Concurrency Control) 支持JSON和XML格式 Postgres-XL缺少的功能 内建的高可用机制 使用外部机制实现高可能,如:Corosync/Pacemaker 有未来功能提升的空间 增加节点/重新分片数据(re-shard)的简便性 数据重分布(redistribution)期间会锁表 可采用预分片(pre-shard)方式解决,在同台物理服务器上建立多个数据节点,每个节点存储一个数据分片。数据重分布时,将一些数据节点迁出即可 某些外键、唯一性约束功能 Postgres-XL架构 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-M9lFuEIP-1640133702200)(./assets/postgre-xl.jpg)] 基于开源项目Postgres-XC XL增加了MPP,允许数据节点间直接通讯,交换复杂跨节点关联查询相关数据信息,减少协调器负载。 多个协调器(Coordinator) 应用程序的数据库连入点 分析查询语句,生成执行计划 多个数据节点(DataNode) 实际的数据存储 数据自动打散分布到集群中各数据节点 本地执行查询 一个查询在所有相关节点上并行查询 全局事务管理器(GTM:Global Transaction Manager) 提供事务间一致性视图 部署GTM Proxy实例,以提高性能 Postgre-XL主要组件 GTM (Global Transaction Manager) - 全局事务管理器 GTM是Postgres-XL的一个关键组件,用于提供一致的事务管理和元组可见性控制。 GTM Standby GTM的备节点,在pgxc,pgxl中,GTM控制所有的全局事务分配,如果出现问题,就会导致整个集群不可用,为了增加可用性,增加该备用节点。当GTM出现问题时,GTM Standby可以升级为GTM,保证集群正常工作。 GTM-Proxy GTM需要与所有的Coordinators通信,为了降低压力,可以在每个Coordinator机器上部署一个GTM-Proxy。 Coordinator --协调器 协调器是应用程序到数据库的接口。它的作用类似于传统的PostgreSQL后台进程,但是协调器不存储任何实际数据。实际数据由数据节点存储。协调器接收SQL语句,根据需要获取全局事务Id和全局快照,确定涉及哪些数据节点,并要求它们执行(部分)语句。当向数据节点发出语句时,它与GXID和全局快照相关联,以便多版本并发控制(MVCC)属性扩展到集群范围。 Datanode --数据节点 用于实际存储数据。表可以分布在各个数据节点之间,也可以复制到所有数据节点。数据节点没有整个数据库的全局视图,它只负责本地存储的数据。接下来,协调器将检查传入语句,并制定子计划。然后,根据需要将这些数据连同GXID和全局快照一起传输到涉及的每个数据节点。数据节点可以在不同的会话中接收来自各个协调器的请求。但是,由于每个事务都是惟一标识的,并且与一致的(全局)快照相关联,所以每个数据节点都可以在其事务和快照上下文中正确执行。 Postgres-XL继承了PostgreSQL Postgres-XL是PostgreSQL的扩展并继承了其很多特性: 复杂查询 外键 触发器 视图 事务 MVCC(多版本控制) 此外,类似于PostgreSQL,用户可以通过多种方式扩展Postgres-XL,例如添加新的 数据类型 函数 操作 聚合函数 索引类型 过程语言 安装 环境说明 由于资源有限,gtm一台、另外两台身兼数职。 主机名 IP 角色 端口 nodename 数据目录 gtm 192.168.20.132 GTM 6666 gtm /nodes/gtm 协调器 5432 coord1 /nodes/coordinator xl1 192.168.20.133 数据节点 5433 node1 /nodes/pgdata gtm代理 6666 gtmpoxy01 /nodes/gtm_pxy1 协调器 5432 coord2 /nodes/coordinator xl2 192.168.20.134 数据节点 5433 node2 /nodes/pgdata gtm代理 6666 gtmpoxy02 /nodes/gtm_pxy2 要求 GNU make版本 3.8及以上版本 [root@pg ~] make --versionGNU Make 3.82Built for x86_64-redhat-linux-gnuCopyright (C) 2010 Free Software Foundation, Inc.License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>This is free software: you are free to change and redistribute it.There is NO WARRANTY, to the extent permitted by law. 需安装GCC包 需安装tar包 用于解压缩文件 默认需要GNU Readline library 其作用是可以让psql命令行记住执行过的命令,并且可以通过键盘上下键切换命令。但是可以通过--without-readline禁用这个特性,或者可以指定--withlibedit-preferred选项来使用libedit 默认使用zlib压缩库 可通过--without-zlib选项来禁用 配置hosts 所有主机上都配置 [root@xl2 11] cat /etc/hosts127.0.0.1 localhost192.168.20.132 gtm192.168.20.133 xl1192.168.20.134 xl2 关闭防火墙、Selinux 所有主机都执行 关闭防火墙: [root@gtm ~] systemctl stop firewalld.service[root@gtm ~] systemctl disable firewalld.service selinux设置: [root@gtm ~]vim /etc/selinux/config 设置SELINUX=disabled,保存退出。 This file controls the state of SELinux on the system. SELINUX= can take one of these three values: enforcing - SELinux security policy is enforced. permissive - SELinux prints warnings instead of enforcing. disabled - No SELinux policy is loaded.SELINUX=disabled SELINUXTYPE= can take one of three two values: targeted - Targeted processes are protected, minimum - Modification of targeted policy. Only selected processes are protected. mls - Multi Level Security protection. 安装依赖包 所有主机上都执行 yum install -y flex bison readline-devel zlib-devel openjade docbook-style-dsssl gcc 创建用户 所有主机上都执行 [root@gtm ~] useradd postgres[root@gtm ~] passwd postgres[root@gtm ~] su - postgres[root@gtm ~] mkdir ~/.ssh[root@gtm ~] chmod 700 ~/.ssh 配置SSH免密登录 仅仅在gtm节点配置如下操作: [root@gtm ~] su - postgres[postgres@gtm ~] ssh-keygen -t rsa[postgres@gtm ~] cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys[postgres@gtm ~] chmod 600 ~/.ssh/authorized_keys 将刚生成的认证文件拷贝到xl1到xl2中,使得gtm节点可以免密码登录xl1~xl2的任意一个节点: [postgres@gtm ~] scp ~/.ssh/authorized_keys postgres@xl1:~/.ssh/[postgres@gtm ~] scp ~/.ssh/authorized_keys postgres@xl2:~/.ssh/ 对所有提示都不要输入,直接enter下一步。直到最后,因为第一次要求输入目标机器的用户密码,输入即可。 下载源码 下载地址:https://www.postgres-xl.org/download/ [root@slave ~] ll postgres-xl-10r1.1.tar.gz-rw-r--r-- 1 root root 28121666 May 30 05:21 postgres-xl-10r1.1.tar.gz 编译、安装Postgres-XL 所有节点都安装,编译需要一点时间,最好同时进行编译。 [root@slave ~] tar xvf postgres-xl-10r1.1.tar.gz[root@slave ~] ./configure --prefix=/home/postgres/pgxl/[root@slave ~] make[root@slave ~] make install[root@slave ~] cd contrib/ --安装必要的工具,在gtm节点上安装即可[root@slave ~] make[root@slave ~] make install 配置环境变量 所有节点都要配置 进入postgres用户,修改其环境变量,开始编辑 [root@gtm ~]su - postgres[postgres@gtm ~]vi .bashrc --不是.bash_profile 在打开的文件末尾,新增如下变量配置: export PGHOME=/home/postgres/pgxlexport LD_LIBRARY_PATH=$PGHOME/lib:$LD_LIBRARY_PATHexport PATH=$PGHOME/bin:$PATH 按住esc,然后输入:wq!保存退出。输入以下命令对更改重启生效。 [postgres@gtm ~] source .bashrc --不是.bash_profile 输入以下语句,如果输出变量结果,代表生效 [postgres@gtm ~] echo $PGHOME 应该输出/home/postgres/pgxl代表生效 配置集群 生成pgxc_ctl.conf配置文件 [postgres@gtm ~] pgxc_ctl prepare/bin/bashInstalling pgxc_ctl_bash script as /home/postgres/pgxl/pgxc_ctl/pgxc_ctl_bash.ERROR: File "/home/postgres/pgxl/pgxc_ctl/pgxc_ctl.conf" not found or not a regular file. No such file or directoryInstalling pgxc_ctl_bash script as /home/postgres/pgxl/pgxc_ctl/pgxc_ctl_bash.Reading configuration using /home/postgres/pgxl/pgxc_ctl/pgxc_ctl_bash --home /home/postgres/pgxl/pgxc_ctl --configuration /home/postgres/pgxl/pgxc_ctl/pgxc_ctl.confFinished reading configuration. PGXC_CTL START Current directory: /home/postgres/pgxl/pgxc_ctl 配置pgxc_ctl.conf 新建/home/postgres/pgxc_ctl/pgxc_ctl.conf文件,编辑如下: 对着模板文件一个一个修改,否则会造成初始化过程出现各种神奇问题。 pgxcInstallDir=$PGHOMEpgxlDATA=$PGHOME/data pgxcOwner=postgres---- GTM Master -----------------------------------------gtmName=gtmgtmMasterServer=gtmgtmMasterPort=6666gtmMasterDir=$pgxlDATA/nodes/gtmgtmSlave=y Specify y if you configure GTM Slave. Otherwise, GTM slave will not be configured and all the following variables will be reset.gtmSlaveName=gtmSlavegtmSlaveServer=gtm value none means GTM slave is not available. Give none if you don't configure GTM Slave.gtmSlavePort=20001 Not used if you don't configure GTM slave.gtmSlaveDir=$pgxlDATA/nodes/gtmSlave Not used if you don't configure GTM slave.---- GTM-Proxy Master -------gtmProxyDir=$pgxlDATA/nodes/gtm_proxygtmProxy=y gtmProxyNames=(gtm_pxy1 gtm_pxy2) gtmProxyServers=(xl1 xl2) gtmProxyPorts=(6666 6666) gtmProxyDirs=($gtmProxyDir $gtmProxyDir) ---- Coordinators ---------coordMasterDir=$pgxlDATA/nodes/coordcoordNames=(coord1 coord2) coordPorts=(5432 5432) poolerPorts=(6667 6667) coordPgHbaEntries=(0.0.0.0/0)coordMasterServers=(xl1 xl2) coordMasterDirs=($coordMasterDir $coordMasterDir)coordMaxWALsernder=0 没设置备份节点,设置为0coordMaxWALSenders=($coordMaxWALsernder $coordMaxWALsernder) 数量保持和coordMasterServers一致coordSlave=n---- Datanodes ----------datanodeMasterDir=$pgxlDATA/nodes/dn_masterprimaryDatanode=xl1 主数据节点datanodeNames=(node1 node2)datanodePorts=(5433 5433) datanodePoolerPorts=(6668 6668) datanodePgHbaEntries=(0.0.0.0/0)datanodeMasterServers=(xl1 xl2)datanodeMasterDirs=($datanodeMasterDir $datanodeMasterDir)datanodeMaxWalSender=4datanodeMaxWALSenders=($datanodeMaxWalSender $datanodeMaxWalSender) 集群初始化,启动,停止 初始化 pgxc_ctl -c /home/postgres/pgxc_ctl/pgxc_ctl.conf init all 输出结果: /bin/bashInstalling pgxc_ctl_bash script as /home/postgres/pgxc_ctl/pgxc_ctl_bash.Installing pgxc_ctl_bash script as /home/postgres/pgxc_ctl/pgxc_ctl_bash.Reading configuration using /home/postgres/pgxc_ctl/pgxc_ctl_bash --home /home/postgres/pgxc_ctl --configuration /home/postgres/pgxc_ctl/pgxc_ctl.conf/home/postgres/pgxc_ctl/pgxc_ctl.conf: line 189: $coordExtraConfig: ambiguous redirectFinished reading configuration. PGXC_CTL START Current directory: /home/postgres/pgxc_ctlStopping all the coordinator masters.Stopping coordinator master coord1.Stopping coordinator master coord2.pg_ctl: directory "/home/postgres/pgxc/nodes/coord/coord1" does not existpg_ctl: directory "/home/postgres/pgxc/nodes/coord/coord2" does not existDone.Stopping all the datanode masters.Stopping datanode master datanode1.Stopping datanode master datanode2.pg_ctl: PID file "/home/postgres/pgxc/nodes/datanode/datanode1/postmaster.pid" does not existIs server running?Done.Stop GTM masterwaiting for server to shut down.... doneserver stopped[postgres@gtm ~]$ echo $PGHOME/home/postgres/pgxl[postgres@gtm ~]$ ll /home/postgres/pgxl/pgxc/nodes/gtm/gtm.^C[postgres@gtm ~]$ pgxc_ctl -c /home/postgres/pgxc_ctl/pgxc_ctl.conf init all/bin/bashInstalling pgxc_ctl_bash script as /home/postgres/pgxc_ctl/pgxc_ctl_bash.Installing pgxc_ctl_bash script as /home/postgres/pgxc_ctl/pgxc_ctl_bash.Reading configuration using /home/postgres/pgxc_ctl/pgxc_ctl_bash --home /home/postgres/pgxc_ctl --configuration /home/postgres/pgxc_ctl/pgxc_ctl.conf/home/postgres/pgxc_ctl/pgxc_ctl.conf: line 189: $coordExtraConfig: ambiguous redirectFinished reading configuration. PGXC_CTL START Current directory: /home/postgres/pgxc_ctlInitialize GTM masterERROR: target directory (/home/postgres/pgxc/nodes/gtm) exists and not empty. Skip GTM initilializationDone.Start GTM masterserver startingInitialize all the coordinator masters.Initialize coordinator master coord1.ERROR: target coordinator master coord1 is running now. Skip initilialization.Initialize coordinator master coord2.The files belonging to this database system will be owned by user "postgres".This user must also own the server process.The database cluster will be initialized with locale "en_US.UTF-8".The default database encoding has accordingly been set to "UTF8".The default text search configuration will be set to "english".Data page checksums are disabled.fixing permissions on existing directory /home/postgres/pgxc/nodes/coord/coord2 ... okcreating subdirectories ... okselecting default max_connections ... 100selecting default shared_buffers ... 128MBselecting dynamic shared memory implementation ... posixcreating configuration files ... okrunning bootstrap script ... okperforming post-bootstrap initialization ... creating cluster information ... oksyncing data to disk ... okfreezing database template0 ... okfreezing database template1 ... okfreezing database postgres ... okWARNING: enabling "trust" authentication for local connectionsYou can change this by editing pg_hba.conf or using the option -A, or--auth-local and --auth-host, the next time you run initdb.Success.Done.Starting coordinator master.Starting coordinator master coord1ERROR: target coordinator master coord1 is already running now. Skip initialization.Starting coordinator master coord22019-05-30 21:09:25.562 EDT [2148] LOG: listening on IPv4 address "0.0.0.0", port 54322019-05-30 21:09:25.562 EDT [2148] LOG: listening on IPv6 address "::", port 54322019-05-30 21:09:25.563 EDT [2148] LOG: listening on Unix socket "/tmp/.s.PGSQL.5432"2019-05-30 21:09:25.601 EDT [2149] LOG: database system was shut down at 2019-05-30 21:09:22 EDT2019-05-30 21:09:25.605 EDT [2148] LOG: database system is ready to accept connections2019-05-30 21:09:25.612 EDT [2156] LOG: cluster monitor startedDone.Initialize all the datanode masters.Initialize the datanode master datanode1.Initialize the datanode master datanode2.The files belonging to this database system will be owned by user "postgres".This user must also own the server process.The database cluster will be initialized with locale "en_US.UTF-8".The default database encoding has accordingly been set to "UTF8".The default text search configuration will be set to "english".Data page checksums are disabled.fixing permissions on existing directory /home/postgres/pgxc/nodes/datanode/datanode1 ... okcreating subdirectories ... okselecting default max_connections ... 100selecting default shared_buffers ... 128MBselecting dynamic shared memory implementation ... posixcreating configuration files ... okrunning bootstrap script ... okperforming post-bootstrap initialization ... creating cluster information ... oksyncing data to disk ... okfreezing database template0 ... okfreezing database template1 ... okfreezing database postgres ... okWARNING: enabling "trust" authentication for local connectionsYou can change this by editing pg_hba.conf or using the option -A, or--auth-local and --auth-host, the next time you run initdb.Success.The files belonging to this database system will be owned by user "postgres".This user must also own the server process.The database cluster will be initialized with locale "en_US.UTF-8".The default database encoding has accordingly been set to "UTF8".The default text search configuration will be set to "english".Data page checksums are disabled.fixing permissions on existing directory /home/postgres/pgxc/nodes/datanode/datanode2 ... okcreating subdirectories ... okselecting default max_connections ... 100selecting default shared_buffers ... 128MBselecting dynamic shared memory implementation ... posixcreating configuration files ... okrunning bootstrap script ... okperforming post-bootstrap initialization ... creating cluster information ... oksyncing data to disk ... okfreezing database template0 ... okfreezing database template1 ... okfreezing database postgres ... okWARNING: enabling "trust" authentication for local connectionsYou can change this by editing pg_hba.conf or using the option -A, or--auth-local and --auth-host, the next time you run initdb.Success.Done.Starting all the datanode masters.Starting datanode master datanode1.WARNING: datanode master datanode1 is running now. Skipping.Starting datanode master datanode2.2019-05-30 21:09:33.352 EDT [2404] LOG: listening on IPv4 address "0.0.0.0", port 154322019-05-30 21:09:33.352 EDT [2404] LOG: listening on IPv6 address "::", port 154322019-05-30 21:09:33.355 EDT [2404] LOG: listening on Unix socket "/tmp/.s.PGSQL.15432"2019-05-30 21:09:33.392 EDT [2404] LOG: redirecting log output to logging collector process2019-05-30 21:09:33.392 EDT [2404] HINT: Future log output will appear in directory "pg_log".Done.psql: FATAL: no pg_hba.conf entry for host "192.168.20.132", user "postgres", database "postgres"psql: FATAL: no pg_hba.conf entry for host "192.168.20.132", user "postgres", database "postgres"Done.psql: FATAL: no pg_hba.conf entry for host "192.168.20.132", user "postgres", database "postgres"psql: FATAL: no pg_hba.conf entry for host "192.168.20.132", user "postgres", database "postgres"Done.[postgres@gtm ~]$ pgxc_ctl -c /home/postgres/pgxc_ctl/pgxc_ctl.conf stop all/bin/bashInstalling pgxc_ctl_bash script as /home/postgres/pgxc_ctl/pgxc_ctl_bash.Installing pgxc_ctl_bash script as /home/postgres/pgxc_ctl/pgxc_ctl_bash.Reading configuration using /home/postgres/pgxc_ctl/pgxc_ctl_bash --home /home/postgres/pgxc_ctl --configuration /home/postgres/pgxc_ctl/pgxc_ctl.conf/home/postgres/pgxc_ctl/pgxc_ctl.conf: line 189: $coordExtraConfig: ambiguous redirectFinished reading configuration. PGXC_CTL START Current directory: /home/postgres/pgxc_ctlStopping all the coordinator masters.Stopping coordinator master coord1.Stopping coordinator master coord2.pg_ctl: directory "/home/postgres/pgxc/nodes/coord/coord1" does not existDone.Stopping all the datanode masters.Stopping datanode master datanode1.Stopping datanode master datanode2.pg_ctl: PID file "/home/postgres/pgxc/nodes/datanode/datanode1/postmaster.pid" does not existIs server running?Done.Stop GTM masterwaiting for server to shut down.... doneserver stopped[postgres@gtm ~]$ pgxc_ctl/bin/bashInstalling pgxc_ctl_bash script as /home/postgres/pgxc_ctl/pgxc_ctl_bash.Installing pgxc_ctl_bash script as /home/postgres/pgxc_ctl/pgxc_ctl_bash.Reading configuration using /home/postgres/pgxc_ctl/pgxc_ctl_bash --home /home/postgres/pgxc_ctl --configuration /home/postgres/pgxc_ctl/pgxc_ctl.conf/home/postgres/pgxc_ctl/pgxc_ctl.conf: line 189: $coordExtraConfig: ambiguous redirectFinished reading configuration. PGXC_CTL START Current directory: /home/postgres/pgxc_ctlPGXC monitor allNot running: gtm masterRunning: coordinator master coord1Not running: coordinator master coord2Running: datanode master datanode1Not running: datanode master datanode2PGXC stop coordinator master coord1Stopping coordinator master coord1.pg_ctl: directory "/home/postgres/pgxc/nodes/coord/coord1" does not existDone.PGXC stop datanode master datanode1Stopping datanode master datanode1.pg_ctl: PID file "/home/postgres/pgxc/nodes/datanode/datanode1/postmaster.pid" does not existIs server running?Done.PGXC monitor allNot running: gtm masterRunning: coordinator master coord1Not running: coordinator master coord2Running: datanode master datanode1Not running: datanode master datanode2PGXC monitor allNot running: gtm masterNot running: coordinator master coord1Not running: coordinator master coord2Not running: datanode master datanode1Not running: datanode master datanode2PGXC exit[postgres@gtm ~]$ pgxc_ctl -c /home/postgres/pgxc_ctl/pgxc_ctl.conf init all/bin/bashInstalling pgxc_ctl_bash script as /home/postgres/pgxc_ctl/pgxc_ctl_bash.Installing pgxc_ctl_bash script as /home/postgres/pgxc_ctl/pgxc_ctl_bash.Reading configuration using /home/postgres/pgxc_ctl/pgxc_ctl_bash --home /home/postgres/pgxc_ctl --configuration /home/postgres/pgxc_ctl/pgxc_ctl.conf/home/postgres/pgxc_ctl/pgxc_ctl.conf: line 189: $coordExtraConfig: ambiguous redirectFinished reading configuration. PGXC_CTL START Current directory: /home/postgres/pgxc_ctlInitialize GTM masterERROR: target directory (/home/postgres/pgxc/nodes/gtm) exists and not empty. Skip GTM initilializationDone.Start GTM masterserver startingInitialize all the coordinator masters.Initialize coordinator master coord1.Initialize coordinator master coord2.The files belonging to this database system will be owned by user "postgres".This user must also own the server process.The database cluster will be initialized with locale "en_US.UTF-8".The default database encoding has accordingly been set to "UTF8".The default text search configuration will be set to "english".Data page checksums are disabled.fixing permissions on existing directory /home/postgres/pgxc/nodes/coord/coord1 ... okcreating subdirectories ... okselecting default max_connections ... 100selecting default shared_buffers ... 128MBselecting dynamic shared memory implementation ... posixcreating configuration files ... okrunning bootstrap script ... okperforming post-bootstrap initialization ... creating cluster information ... oksyncing data to disk ... okfreezing database template0 ... okfreezing database template1 ... okfreezing database postgres ... okWARNING: enabling "trust" authentication for local connectionsYou can change this by editing pg_hba.conf or using the option -A, or--auth-local and --auth-host, the next time you run initdb.Success.The files belonging to this database system will be owned by user "postgres".This user must also own the server process.The database cluster will be initialized with locale "en_US.UTF-8".The default database encoding has accordingly been set to "UTF8".The default text search configuration will be set to "english".Data page checksums are disabled.fixing permissions on existing directory /home/postgres/pgxc/nodes/coord/coord2 ... okcreating subdirectories ... okselecting default max_connections ... 100selecting default shared_buffers ... 128MBselecting dynamic shared memory implementation ... posixcreating configuration files ... okrunning bootstrap script ... okperforming post-bootstrap initialization ... creating cluster information ... oksyncing data to disk ... okfreezing database template0 ... okfreezing database template1 ... okfreezing database postgres ... okWARNING: enabling "trust" authentication for local connectionsYou can change this by editing pg_hba.conf or using the option -A, or--auth-local and --auth-host, the next time you run initdb.Success.Done.Starting coordinator master.Starting coordinator master coord1Starting coordinator master coord22019-05-30 21:13:03.998 EDT [25137] LOG: listening on IPv4 address "0.0.0.0", port 54322019-05-30 21:13:03.998 EDT [25137] LOG: listening on IPv6 address "::", port 54322019-05-30 21:13:04.000 EDT [25137] LOG: listening on Unix socket "/tmp/.s.PGSQL.5432"2019-05-30 21:13:04.038 EDT [25138] LOG: database system was shut down at 2019-05-30 21:13:00 EDT2019-05-30 21:13:04.042 EDT [25137] LOG: database system is ready to accept connections2019-05-30 21:13:04.049 EDT [25145] LOG: cluster monitor started2019-05-30 21:13:04.020 EDT [2730] LOG: listening on IPv4 address "0.0.0.0", port 54322019-05-30 21:13:04.020 EDT [2730] LOG: listening on IPv6 address "::", port 54322019-05-30 21:13:04.021 EDT [2730] LOG: listening on Unix socket "/tmp/.s.PGSQL.5432"2019-05-30 21:13:04.057 EDT [2731] LOG: database system was shut down at 2019-05-30 21:13:00 EDT2019-05-30 21:13:04.061 EDT [2730] LOG: database system is ready to accept connections2019-05-30 21:13:04.062 EDT [2738] LOG: cluster monitor startedDone.Initialize all the datanode masters.Initialize the datanode master datanode1.Initialize the datanode master datanode2.The files belonging to this database system will be owned by user "postgres".This user must also own the server process.The database cluster will be initialized with locale "en_US.UTF-8".The default database encoding has accordingly been set to "UTF8".The default text search configuration will be set to "english".Data page checksums are disabled.fixing permissions on existing directory /home/postgres/pgxc/nodes/datanode/datanode1 ... okcreating subdirectories ... okselecting default max_connections ... 100selecting default shared_buffers ... 128MBselecting dynamic shared memory implementation ... posixcreating configuration files ... okrunning bootstrap script ... okperforming post-bootstrap initialization ... creating cluster information ... oksyncing data to disk ... okfreezing database template0 ... okfreezing database template1 ... okfreezing database postgres ... okWARNING: enabling "trust" authentication for local connectionsYou can change this by editing pg_hba.conf or using the option -A, or--auth-local and --auth-host, the next time you run initdb.Success.The files belonging to this database system will be owned by user "postgres".This user must also own the server process.The database cluster will be initialized with locale "en_US.UTF-8".The default database encoding has accordingly been set to "UTF8".The default text search configuration will be set to "english".Data page checksums are disabled.fixing permissions on existing directory /home/postgres/pgxc/nodes/datanode/datanode2 ... okcreating subdirectories ... okselecting default max_connections ... 100selecting default shared_buffers ... 128MBselecting dynamic shared memory implementation ... posixcreating configuration files ... okrunning bootstrap script ... okperforming post-bootstrap initialization ... creating cluster information ... oksyncing data to disk ... okfreezing database template0 ... okfreezing database template1 ... okfreezing database postgres ... okWARNING: enabling "trust" authentication for local connectionsYou can change this by editing pg_hba.conf or using the option -A, or--auth-local and --auth-host, the next time you run initdb.Success.Done.Starting all the datanode masters.Starting datanode master datanode1.Starting datanode master datanode2.2019-05-30 21:13:12.077 EDT [25392] LOG: listening on IPv4 address "0.0.0.0", port 154322019-05-30 21:13:12.077 EDT [25392] LOG: listening on IPv6 address "::", port 154322019-05-30 21:13:12.079 EDT [25392] LOG: listening on Unix socket "/tmp/.s.PGSQL.15432"2019-05-30 21:13:12.114 EDT [25392] LOG: redirecting log output to logging collector process2019-05-30 21:13:12.114 EDT [25392] HINT: Future log output will appear in directory "pg_log".2019-05-30 21:13:12.079 EDT [2985] LOG: listening on IPv4 address "0.0.0.0", port 154322019-05-30 21:13:12.079 EDT [2985] LOG: listening on IPv6 address "::", port 154322019-05-30 21:13:12.081 EDT [2985] LOG: listening on Unix socket "/tmp/.s.PGSQL.15432"2019-05-30 21:13:12.117 EDT [2985] LOG: redirecting log output to logging collector process2019-05-30 21:13:12.117 EDT [2985] HINT: Future log output will appear in directory "pg_log".Done.psql: FATAL: no pg_hba.conf entry for host "192.168.20.132", user "postgres", database "postgres"psql: FATAL: no pg_hba.conf entry for host "192.168.20.132", user "postgres", database "postgres"Done.psql: FATAL: no pg_hba.conf entry for host "192.168.20.132", user "postgres", database "postgres"psql: FATAL: no pg_hba.conf entry for host "192.168.20.132", user "postgres", database "postgres"Done. 启动 pgxc_ctl -c /home/postgres/pgxc_ctl/pgxc_ctl.conf start all 关闭 pgxc_ctl -c /home/postgres/pgxc_ctl/pgxc_ctl.conf stop all 查看集群状态 [postgres@gtm ~]$ pgxc_ctl monitor all/bin/bashInstalling pgxc_ctl_bash script as /home/postgres/pgxc_ctl/pgxc_ctl_bash.Installing pgxc_ctl_bash script as /home/postgres/pgxc_ctl/pgxc_ctl_bash.Reading configuration using /home/postgres/pgxc_ctl/pgxc_ctl_bash --home /home/postgres/pgxc_ctl --configuration /home/postgres/pgxc_ctl/pgxc_ctl.conf/home/postgres/pgxc_ctl/pgxc_ctl.conf: line 189: $coordExtraConfig: ambiguous redirectFinished reading configuration. PGXC_CTL START Current directory: /home/postgres/pgxc_ctlRunning: gtm masterRunning: coordinator master coord1Running: coordinator master coord2Running: datanode master datanode1Running: datanode master datanode2 配置集群信息 分别在数据节点、协调器节点上分别执行以下命令: 注:本节点只执行修改操作即可(alert node),其他节点执行创建命令(create node)。因为本节点已经包含本节点的信息。 create node coord1 with (type=coordinator,host=xl1, port=5432);create node coord2 with (type=coordinator,host=xl2, port=5432);alter node coord1 with (type=coordinator,host=xl1, port=5432);alter node coord2 with (type=coordinator,host=xl2, port=5432);create node datanode1 with (type=datanode, host=xl1,port=15432,primary=true,PREFERRED);create node datanode2 with (type=datanode, host=xl2,port=15432);alter node datanode1 with (type=datanode, host=xl1,port=15432,primary=true,PREFERRED);alter node datanode2 with (type=datanode, host=xl2,port=15432);select pgxc_pool_reload(); 分别登陆数据节点、协调器节点验证 postgres= select from pgxc_node;node_name | node_type | node_port | node_host | nodeis_primary | nodeis_preferred | node_id-----------+-----------+-----------+-----------+----------------+------------------+-------------coord1 | C | 5432 | xl1 | f | f | 1885696643coord2 | C | 5432 | xl2 | f | f | -1197102633datanode2 | D | 15432 | xl2 | f | f | -905831925datanode1 | D | 15432 | xl1 | t | f | 888802358(4 rows) 测试 插入数据 在数据节点1,执行相关操作。 通过协调器端口登录PG [postgres@xl1 ~]$ psql -p 5432psql (PGXL 10r1.1, based on PG 10.6 (Postgres-XL 10r1.1))Type "help" for help.postgres= create database lei;CREATE DATABASEpostgres= \c lei;You are now connected to database "lei" as user "postgres".lei= create table test1(id int,name text);CREATE TABLElei= insert into test1(id,name) select generate_series(1,8),'测试';INSERT 0 8lei= select from test1;id | name----+------1 | 测试2 | 测试5 | 测试6 | 测试8 | 测试3 | 测试4 | 测试7 | 测试(8 rows) 注:默认创建的表为分布式表,也就是每个数据节点值存储表的部分数据。关于表类型具体说明,下面有说明。 通过15432端口登录数据节点,查看数据 有5条数据 [postgres@xl1 ~]$ psql -p 15432psql (PGXL 10r1.1, based on PG 10.6 (Postgres-XL 10r1.1))Type "help" for help.postgres= \c lei;You are now connected to database "lei" as user "postgres".lei= select from test1;id | name----+------1 | 测试2 | 测试5 | 测试6 | 测试8 | 测试(5 rows) 登录到节点2,查看数据 有3条数据 [postgres@xl2 ~]$ psql -p15432psql (PGXL 10r1.1, based on PG 10.6 (Postgres-XL 10r1.1))Type "help" for help.postgres= \c lei;You are now connected to database "lei" as user "postgres".lei= select from test1;id | name----+------3 | 测试4 | 测试7 | 测试(3 rows) 两个节点的数据加起来整个8条,没有问题。 至此Postgre-XL集群搭建完成。 创建数据库、表时可能会出现以下错误: ERROR: Failed to get pooled connections 是因为pg_hba.conf配置不对,所有节点加上host all all 192.168.20.0/0 trust并重启集群即可。 ERROR: No Datanode defined in cluster 首先确认是否创建了数据节点,也就是create node相关的命令。如果创建了则执行select pgxc_pool_reload();使其生效即可。 集群管理与应用 表类型说明 REPLICATION表:各个datanode节点中,表的数据完全相同,也就是说,插入数据时,会分别在每个datanode节点插入相同数据。读数据时,只需要读任意一个datanode节点上的数据。 建表语法: CREATE TABLE repltab (col1 int, col2 int) DISTRIBUTE BY REPLICATION; DISTRIBUTE :会将插入的数据,按照拆分规则,分配到不同的datanode节点中存储,也就是sharding技术。每个datanode节点只保存了部分数据,通过coordinate节点可以查询完整的数据视图。 CREATE TABLE disttab(col1 int, col2 int, col3 text) DISTRIBUTE BY HASH(col1); 模拟数据插入 任意登录一个coordinate节点进行建表操作 [postgres@gtm ~]$ psql -h xl1 -p 5432 -U postgrespostgres= INSERT INTO disttab SELECT generate_series(1,100), generate_series(101, 200), 'foo';INSERT 0 100postgres= INSERT INTO repltab SELECT generate_series(1,100), generate_series(101, 200);INSERT 0 100 查看数据分布结果: DISTRIBUTE表分布结果 postgres= SELECT xc_node_id, count() FROM disttab GROUP BY xc_node_id;xc_node_id | count ------------+-------1148549230 | 42-927910690 | 58(2 rows) REPLICATION表分布结果 postgres= SELECT count() FROM repltab;count -------100(1 row) 查看另一个datanode2中repltab表结果 [postgres@datanode2 pgxl9.5]$ psql -p 15432psql (PGXL 10r1.1, based on PG 10.6 (Postgres-XL 10r1.1))Type "help" for help.postgres= SELECT count() FROM repltab;count -------100(1 row) 结论:REPLICATION表中,datanode1,datanode2中表是全部数据,一模一样。而DISTRIBUTE表,数据散落近乎平均分配到了datanode1,datanode2节点中。 新增数据节点与数据重分布 在线新增节点、并重新分布数据。 新增datanode节点 在gtm集群管理节点上执行pgxc_ctl命令 [postgres@gtm ~]$ pgxc_ctl/bin/bashInstalling pgxc_ctl_bash script as /home/postgres/pgxc_ctl/pgxc_ctl_bash.Installing pgxc_ctl_bash script as /home/postgres/pgxc_ctl/pgxc_ctl_bash.Reading configuration using /home/postgres/pgxc_ctl/pgxc_ctl_bash --home /home/postgres/pgxc_ctl --configuration /home/postgres/pgxc_ctl/pgxc_ctl.confFinished reading configuration. PGXC_CTL START Current directory: /home/postgres/pgxc_ctlPGXC 在服务器xl3上,新增一个master角色的datanode节点,名称是datanode3 端口号暂定5430,pool master暂定6669 ,指定好数据目录位置,从两个节点升级到3个节点,之后要写3个none none应该是datanodeSpecificExtraConfig或者datanodeSpecificExtraPgHba配置PGXC add datanode master datanode3 xl3 15432 6671 /home/postgres/pgxc/nodes/datanode/datanode3 none none none 等待新增完成后,查询集群节点状态: postgres= select from pgxc_node;node_name | node_type | node_port | node_host | nodeis_primary | nodeis_preferred | node_id-----------+-----------+-----------+-----------+----------------+------------------+-------------datanode1 | D | 15432 | xl1 | t | f | 888802358datanode2 | D | 15432 | xl2 | f | f | -905831925datanode3 | D | 15432 | xl3 | f | f | -705831925coord1 | C | 5432 | xl1 | f | f | 1885696643coord2 | C | 5432 | xl2 | f | f | -1197102633(4 rows) 节点新增完毕 数据重新分布 由于新增节点后无法自动完成数据重新分布,需要手动操作。 DISTRIBUTE表分布在了node1,node2节点上,如下: postgres= SELECT xc_node_id, count() FROM disttab GROUP BY xc_node_id;xc_node_id | count ------------+-------1148549230 | 42-927910690 | 58(2 rows) 新增一个节点后,将sharding表数据重新分配到三个节点上,将repl表复制到新节点 重分布sharding表postgres= ALTER TABLE disttab ADD NODE (datanode3);ALTER TABLE 复制数据到新节点postgres= ALTER TABLE repltab ADD NODE (datanode3);ALTER TABLE 查看新的数据分布: postgres= SELECT xc_node_id, count() FROM disttab GROUP BY xc_node_id;xc_node_id | count ------------+--------700122826 | 36-927910690 | 321148549230 | 32(3 rows) 登录datanode3(新增的时候,放在了xl3服务器上,端口15432)节点查看数据: [postgres@gtm ~]$ psql -h xl3 -p 15432 -U postgrespsql (PGXL 10r1.1, based on PG 10.6 (Postgres-XL 10r1.1))Type "help" for help.postgres= select count() from repltab;count -------100(1 row) 很明显,通过 ALTER TABLE tt ADD NODE (dn)命令,可以将DISTRIBUTE表数据重新分布到新节点,重分布过程中会中断所有事务。可以将REPLICATION表数据复制到新节点。 从datanode节点中回收数据 postgres= ALTER TABLE disttab DELETE NODE (datanode3);ALTER TABLEpostgres= ALTER TABLE repltab DELETE NODE (datanode3);ALTER TABLE 删除数据节点 Postgresql-XL并没有检查将被删除的datanode节点是否有replicated/distributed表的数据,为了数据安全,在删除之前需要检查下被删除节点上的数据,有数据的话,要回收掉分配到其他节点,然后才能安全删除。删除数据节点分为四步骤: 1.查询要删除节点dn3的oid postgres= SELECT oid, FROM pgxc_node;oid | node_name | node_type | node_port | node_host | nodeis_primary | nodeis_preferred | node_id -------+-----------+-----------+-----------+-----------+----------------+------------------+-------------11819 | coord1 | C | 5432 | datanode1 | f | f | 188569664316384 | coord2 | C | 5432 | datanode2 | f | f | -119710263316385 | node1 | D | 5433 | datanode1 | f | t | 114854923016386 | node2 | D | 5433 | datanode2 | f | f | -92791069016397 | dn3 | D | 5430 | datanode1 | f | f | -700122826(5 rows) 2.查询dn3对应的oid中是否有数据 testdb= SELECT FROM pgxc_class WHERE nodeoids::integer[] @> ARRAY[16397];pcrelid | pclocatortype | pcattnum | pchashalgorithm | pchashbuckets | nodeoids ---------+---------------+----------+-----------------+---------------+-------------------16388 | H | 1 | 1 | 4096 | 16397 16385 1638616394 | R | 0 | 0 | 0 | 16397 16385 16386(2 rows) 3.有数据的先回收数据 postgres= ALTER TABLE disttab DELETE NODE (dn3);ALTER TABLEpostgres= ALTER TABLE repltab DELETE NODE (dn3);ALTER TABLEpostgres= SELECT FROM pgxc_class WHERE nodeoids::integer[] @> ARRAY[16397];pcrelid | pclocatortype | pcattnum | pchashalgorithm | pchashbuckets | nodeoids ---------+---------------+----------+-----------------+---------------+----------(0 rows) 4.安全删除dn3 PGXC$ remove datanode master dn3 clean 故障节点FAILOVER 1.查看当前集群状态 [postgres@gtm ~]$ psql -h xl1 -p 5432psql (PGXL 10r1.1, based on PG 10.6 (Postgres-XL 10r1.1))Type "help" for help.postgres= SELECT oid, FROM pgxc_node;oid | node_name | node_type | node_port | node_host | nodeis_primary | nodeis_preferred | node_id-------+-----------+-----------+-----------+-----------+----------------+------------------+-------------11739 | coord1 | C | 5432 | xl1 | f | f | 188569664316384 | coord2 | C | 5432 | xl2 | f | f | -119710263316387 | datanode2 | D | 15432 | xl2 | f | f | -90583192516388 | datanode1 | D | 15432 | xl1 | t | t | 888802358(4 rows) 2.模拟datanode1节点故障 直接关闭即可 PGXC stop -m immediate datanode master datanode1Stopping datanode master datanode1.Done. 3.测试查询 只要查询涉及到datanode1上的数据,那么该查询就会报错 postgres= SELECT xc_node_id, count() FROM disttab GROUP BY xc_node_id;WARNING: failed to receive file descriptors for connectionsERROR: Failed to get pooled connectionsHINT: This may happen because one or more nodes are currently unreachable, either because of node or network failure.Its also possible that the target node may have hit the connection limit or the pooler is configured with low connections.Please check if all nodes are running fine and also review max_connections and max_pool_size configuration parameterspostgres= SELECT xc_node_id, FROM disttab WHERE col1 = 3;xc_node_id | col1 | col2 | col3------------+------+------+-------905831925 | 3 | 103 | foo(1 row) 测试发现,查询范围如果涉及到故障的node1节点,会报错,而查询的数据范围不在node1上的话,仍然可以查询。 4.手动切换 要想切换,必须要提前配置slave节点。 PGXC$ failover datanode node1 切换完成后,查询集群 postgres= SELECT oid, FROM pgxc_node;oid | node_name | node_type | node_port | node_host | nodeis_primary | nodeis_preferred | node_id -------+-----------+-----------+-----------+-----------+----------------+------------------+-------------11819 | coord1 | C | 5432 | datanode1 | f | f | 188569664316384 | coord2 | C | 5432 | datanode2 | f | f | -119710263316386 | node2 | D | 15432 | datanode2 | f | f | -92791069016385 | node1 | D | 15433 | datanode2 | f | t | 1148549230(4 rows) 发现datanode1节点的ip和端口都已经替换为配置的slave了。 本篇文章为转载内容。原文链接:https://blog.csdn.net/qianglei6077/article/details/94379331。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-01-30 11:09:03
94
转载
转载文章
...0 1 流表类 流表操作 1.添加普通流表 ovs-ofctl add-flow br0 in_port=1,actions=output:2 1 2.删除所有流表 ovs-ofctl del-flows br0 1 3.按匹配项来删除流表 ovs-ofctl del-flows br0 "in_port=1" 1 匹配项 1.匹配vlan tag,范围为0-4095 ovs-ofctl add-flow br0 priority=401,in_port=1,dl_vlan=777,actions=output:2 1 2.匹配vlan pcp,范围为0-7 ovs-ofctl add-flow br0 priority=401,in_port=1,dl_vlan_pcp=7,actions=output:2 1 3.匹配源/目的MAC ovs-ofctl add-flow br0 in_port=1,dl_src=00:00:00:00:00:01/00:00:00:00:00:01,actions=output:2 ovs-ofctl add-flow br0 in_port=1,dl_dst=00:00:00:00:00:01/00:00:00:00:00:01,actions=output:2 1 2 4.匹配以太网类型,范围为0-65535 ovs-ofctl add-flow br0 in_port=1,dl_type=0x0806,actions=output:2 1 5.匹配源/目的IP 条件:指定dl_type=0x0800,或者ip/tcp ovs-ofctl add-flow br0 ip,in_port=1,nw_src=10.10.0.0/16,actions=output:2 ovs-ofctl add-flow br0 ip,in_port=1,nw_dst=10.20.0.0/16,actions=output:2 1 2 6.匹配协议号,范围为0-255 条件:指定dl_type=0x0800或者ip ICMP ovs-ofctl add-flow br0 ip,in_port=1,nw_proto=1,actions=output:2 7.匹配IP ToS/DSCP,tos范围为0-255,DSCP范围为0-63 条件:指定dl_type=0x0800/0x86dd,并且ToS低2位会被忽略(DSCP值为ToS的高6位,并且低2位为预留位) ovs-ofctl add-flow br0 ip,in_port=1,nw_tos=68,actions=output:2 ovs-ofctl add-flow br0 ip,in_port=1,ip_dscp=62,actions=output:2 8.匹配IP ecn位,范围为0-3 条件:指定dl_type=0x0800/0x86dd ovs-ofctl add-flow br0 ip,in_port=1,ip_ecn=2,actions=output:2 9.匹配IP TTL,范围为0-255 ovs-ofctl add-flow br0 ip,in_port=1,nw_ttl=128,actions=output:2 10.匹配tcp/udp,源/目的端口,范围为0-65535 匹配源tcp端口179 ovs-ofctl add-flow br0 tcp,tcp_src=179/0xfff0,actions=output:2 匹配目的tcp端口179 ovs-ofctl add-flow br0 tcp,tcp_dst=179/0xfff0,actions=output:2 匹配源udp端口1234 ovs-ofctl add-flow br0 udp,udp_src=1234/0xfff0,actions=output:2 匹配目的udp端口1234 ovs-ofctl add-flow br0 udp,udp_dst=1234/0xfff0,actions=output:2 11.匹配tcp flags tcp flags=fin,syn,rst,psh,ack,urg,ece,cwr,ns ovs-ofctl add-flow br0 tcp,tcp_flags=ack,actions=output:2 12.匹配icmp code,范围为0-255 条件:指定icmp ovs-ofctl add-flow br0 icmp,icmp_code=2,actions=output:2 13.匹配vlan TCI TCI低12位为vlan id,高3位为priority,例如tci=0xf123则vlan_id为0x123和vlan_pcp=7 ovs-ofctl add-flow br0 in_port=1,vlan_tci=0xf123,actions=output:2 14.匹配mpls label 条件:指定dl_type=0x8847/0x8848 ovs-ofctl add-flow br0 mpls,in_port=1,mpls_label=7,actions=output:2 15.匹配mpls tc,范围为0-7 条件:指定dl_type=0x8847/0x8848 ovs-ofctl add-flow br0 mpls,in_port=1,mpls_tc=7,actions=output:2 1 16.匹配tunnel id,源/目的IP 匹配tunnel id ovs-ofctl add-flow br0 in_port=1,tun_id=0x7/0xf,actions=output:2 匹配tunnel源IP ovs-ofctl add-flow br0 in_port=1,tun_src=192.168.1.0/255.255.255.0,actions=output:2 匹配tunnel目的IP ovs-ofctl add-flow br0 in_port=1,tun_dst=192.168.1.0/255.255.255.0,actions=output:2 一些匹配项的速记符 速记符 匹配项 ip dl_type=0x800 ipv6 dl_type=0x86dd icmp dl_type=0x0800,nw_proto=1 icmp6 dl_type=0x86dd,nw_proto=58 tcp dl_type=0x0800,nw_proto=6 tcp6 dl_type=0x86dd,nw_proto=6 udp dl_type=0x0800,nw_proto=17 udp6 dl_type=0x86dd,nw_proto=17 sctp dl_type=0x0800,nw_proto=132 sctp6 dl_type=0x86dd,nw_proto=132 arp dl_type=0x0806 rarp dl_type=0x8035 mpls dl_type=0x8847 mplsm dl_type=0x8848 指令动作 1.动作为出接口 从指定接口转发出去 ovs-ofctl add-flow br0 in_port=1,actions=output:2 1 2.动作为指定group group id为已创建的group table ovs-ofctl add-flow br0 in_port=1,actions=group:666 1 3.动作为normal 转为L2/L3处理流程 ovs-ofctl add-flow br0 in_port=1,actions=normal 1 4.动作为flood 从所有物理接口转发出去,除了入接口和已关闭flooding的接口 ovs-ofctl add-flow br0 in_port=1,actions=flood 1 5.动作为all 从所有物理接口转发出去,除了入接口 ovs-ofctl add-flow br0 in_port=1,actions=all 1 6.动作为local 一般是转发给本地网桥 ovs-ofctl add-flow br0 in_port=1,actions=local 1 7.动作为in_port 从入接口转发回去 ovs-ofctl add-flow br0 in_port=1,actions=in_port 1 8.动作为controller 以packet-in消息上送给控制器 ovs-ofctl add-flow br0 in_port=1,actions=controller 1 9.动作为drop 丢弃数据包操作 ovs-ofctl add-flow br0 in_port=1,actions=drop 1 10.动作为mod_vlan_vid 修改报文的vlan id,该选项会使vlan_pcp置为0 ovs-ofctl add-flow br0 in_port=1,actions=mod_vlan_vid:8,output:2 1 11.动作为mod_vlan_pcp 修改报文的vlan优先级,该选项会使vlan_id置为0 ovs-ofctl add-flow br0 in_port=1,actions=mod_vlan_pcp:7,output:2 1 12.动作为strip_vlan 剥掉报文内外层vlan tag ovs-ofctl add-flow br0 in_port=1,actions=strip_vlan,output:2 1 13.动作为push_vlan 在报文外层压入一层vlan tag,需要使用openflow1.1以上版本兼容 ovs-ofctl add-flow -O OpenFlow13 br0 in_port=1,actions=push_vlan:0x8100,set_field:4097-\>vlan_vid,output:2 1 ps: set field值为4096+vlan_id,并且vlan优先级为0,即4096-8191,对应的vlan_id为0-4095 14.动作为push_mpls 修改报文的ethertype,并且压入一个MPLS LSE ovs-ofctl add-flow br0 in_port=1,actions=push_mpls:0x8847,set_field:10-\>mpls_label,output:2 1 15.动作为pop_mpls 剥掉最外层mpls标签,并且修改ethertype为非mpls类型 ovs-ofctl add-flow br0 mpls,in_port=1,mpls_label=20,actions=pop_mpls:0x0800,output:2 1 16.动作为修改源/目的MAC,修改源/目的IP 修改源MAC ovs-ofctl add-flow br0 in_port=1,actions=mod_dl_src:00:00:00:00:00:01,output:2 修改目的MAC ovs-ofctl add-flow br0 in_port=1,actions=mod_dl_dst:00:00:00:00:00:01,output:2 修改源IP ovs-ofctl add-flow br0 in_port=1,actions=mod_nw_src:192.168.1.1,output:2 修改目的IP ovs-ofctl add-flow br0 in_port=1,actions=mod_nw_dst:192.168.1.1,output:2 17.动作为修改TCP/UDP/SCTP源目的端口 修改TCP源端口 ovs-ofctl add-flow br0 tcp,in_port=1,actions=mod_tp_src:67,output:2 修改TCP目的端口 ovs-ofctl add-flow br0 tcp,in_port=1,actions=mod_tp_dst:68,output:2 修改UDP源端口 ovs-ofctl add-flow br0 udp,in_port=1,actions=mod_tp_src:67,output:2 修改UDP目的端口 ovs-ofctl add-flow br0 udp,in_port=1,actions=mod_tp_dst:68,output:2 18.动作为mod_nw_tos 条件:指定dl_type=0x0800 修改ToS字段的高6位,范围为0-255,值必须为4的倍数,并且不会去修改ToS低2位ecn值 ovs-ofctl add-flow br0 ip,in_port=1,actions=mod_nw_tos:68,output:2 1 19.动作为mod_nw_ecn 条件:指定dl_type=0x0800,需要使用openflow1.1以上版本兼容 修改ToS字段的低2位,范围为0-3,并且不会去修改ToS高6位的DSCP值 ovs-ofctl add-flow br0 ip,in_port=1,actions=mod_nw_ecn:2,output:2 1 20.动作为mod_nw_ttl 修改IP报文ttl值,需要使用openflow1.1以上版本兼容 ovs-ofctl add-flow -O OpenFlow13 br0 in_port=1,actions=mod_nw_ttl:6,output:2 1 21.动作为dec_ttl 对IP报文进行ttl自减操作 ovs-ofctl add-flow br0 in_port=1,actions=dec_ttl,output:2 1 22.动作为set_mpls_label 对报文最外层mpls标签进行修改,范围为20bit值 ovs-ofctl add-flow br0 in_port=1,actions=set_mpls_label:666,output:2 1 23.动作为set_mpls_tc 对报文最外层mpls tc进行修改,范围为0-7 ovs-ofctl add-flow br0 in_port=1,actions=set_mpls_tc:7,output:2 1 24.动作为set_mpls_ttl 对报文最外层mpls ttl进行修改,范围为0-255 ovs-ofctl add-flow br0 in_port=1,actions=set_mpls_ttl:255,output:2 1 25.动作为dec_mpls_ttl 对报文最外层mpls ttl进行自减操作 ovs-ofctl add-flow br0 in_port=1,actions=dec_mpls_ttl,output:2 1 26.动作为move NXM字段 使用move参数对NXM字段进行操作 将报文源MAC复制到目的MAC字段,并且将源MAC改为00:00:00:00:00:01 ovs-ofctl add-flow br0 in_port=1,actions=move:NXM_OF_ETH_SRC[]-\>NXM_OF_ETH_DST[],mod_dl_src:00:00:00:00:00:01,output:2 1 2 ps: 常用NXM字段参照表 NXM字段 报文字段 NXM_OF_ETH_SRC 源MAC NXM_OF_ETH_DST 目的MAC NXM_OF_ETH_TYPE 以太网类型 NXM_OF_VLAN_TCI vid NXM_OF_IP_PROTO IP协议号 NXM_OF_IP_TOS IP ToS值 NXM_NX_IP_ECN IP ToS ECN NXM_OF_IP_SRC 源IP NXM_OF_IP_DST 目的IP NXM_OF_TCP_SRC TCP源端口 NXM_OF_TCP_DST TCP目的端口 NXM_OF_UDP_SRC UDP源端口 NXM_OF_UDP_DST UDP目的端口 NXM_OF_SCTP_SRC SCTP源端口 NXM_OF_SCTP_DST SCTP目的端口 27.动作为load NXM字段 使用load参数对NXM字段进行赋值操作 push mpls label,并且把10(0xa)赋值给mpls label ovs-ofctl add-flow br0 in_port=1,actions=push_mpls:0x8847,load:0xa-\>OXM_OF_MPLS_LABEL[],output:2 对目的MAC进行赋值 ovs-ofctl add-flow br0 in_port=1,actions=load:0x001122334455-\>OXM_OF_ETH_DST[],output:2 1 2 3 4 28.动作为pop_vlan 弹出报文最外层vlan tag ovs-ofctl add-flow br0 in_port=1,dl_type=0x8100,dl_vlan=777,actions=pop_vlan,output:2 1 meter表 常用操作 由于meter表是openflow1.3版本以后才支持,所以所有命令需要指定OpenFlow1.3版本以上 ps: 在openvswitch-v2.8之前的版本中,还不支持meter 在v2.8版本之后已经实现,要正常使用的话,需要注意的是datapath类型要指定为netdev,band type暂时只支持drop,还不支持DSCP REMARK 1.查看当前设备对meter的支持 ovs-ofctl -O OpenFlow13 meter-features br0 2.查看meter表 ovs-ofctl -O OpenFlow13 dump-meters br0 3.查看meter统计 ovs-ofctl -O OpenFlow13 meter-stats br0 4.创建meter表 限速类型以kbps(kilobits per second)计算,超过20kb/s则丢弃 ovs-ofctl -O OpenFlow13 add-meter br0 meter=1,kbps,band=type=drop,rate=20 同上,增加burst size参数 ovs-ofctl -O OpenFlow13 add-meter br0 meter=2,kbps,band=type=drop,rate=20,burst_size=256 同上,增加stats参数,对meter进行计数统计 ovs-ofctl -O OpenFlow13 add-meter br0 meter=3,kbps,stats,band=type=drop,rate=20,burst_size=256 限速类型以pktps(packets per second)计算,超过1000pkt/s则丢弃 ovs-ofctl -O OpenFlow13 add-meter br0 meter=4,pktps,band=type=drop,rate=1000 5.删除meter表 删除全部meter表 ovs-ofctl -O OpenFlow13 del-meters br0 删除meter id=1 ovs-ofctl -O OpenFlow13 del-meter br0 meter=1 6.创建流表 ovs-ofctl -O OpenFlow13 add-flow br0 in_port=1,actions=meter:1,output:2 group表 由于group表是openflow1.1版本以后才支持,所以所有命令需要指定OpenFlow1.1版本以上 常用操作 group table支持4种类型 all:所有buckets都执行一遍 select: 每次选择其中一个bucket执行,常用于负载均衡应用 ff(FAST FAILOVER):快速故障修复,用于检测解决接口等故障 indirect:间接执行,类似于一个函数方法,被另一个group来调用 1.查看当前设备对group的支持 ovs-ofctl -O OpenFlow13 dump-group-features br0 2.查看group表 ovs-ofctl -O OpenFlow13 dump-groups br0 3.创建group表 类型为all ovs-ofctl -O OpenFlow13 add-group br0 group_id=1,type=all,bucket=output:1,bucket=output:2,bucket=output:3 类型为select ovs-ofctl -O OpenFlow13 add-group br0 group_id=2,type=select,bucket=output:1,bucket=output:2,bucket=output:3 类型为select,指定hash方法(5元组,OpenFlow1.5+) ovs-ofctl -O OpenFlow15 add-group br0 group_id=3,type=select,selection_method=hash,fields=ip_src,bucket=output:2,bucket=output:3 4.删除group表 ovs-ofctl -O OpenFlow13 del-groups br0 group_id=2 5.创建流表 ovs-ofctl -O OpenFlow13 add-flow br0 in_port=1,actions=group:2 goto table配置 数据流先从table0开始匹配,如actions有goto_table,再进行后续table的匹配,实现多级流水线,如需使用goto table,则创建流表时,指定table id,范围为0-255,不指定则默认为table0 1.在table0中添加一条流表条目 ovs-ofctl add-flow br0 table=0,in_port=1,actions=goto_table=1 2.在table1中添加一条流表条目 ovs-ofctl add-flow br0 table=1,ip,nw_dst=10.10.0.0/16,actions=output:2 tunnel配置 如需配置tunnel,必需确保当前系统对各tunnel的remote ip网络可达 gre 1.创建一个gre接口,并且指定端口id=1001 ovs-vsctl add-port br0 gre1 -- set Interface gre1 type=gre options:remote_ip=1.1.1.1 ofport_request=1001 2.可选选项 将tos或者ttl在隧道上继承,并将tunnel id设置成123 ovs-vsctl set Interface gre1 options:tos=inherit options:ttl=inherit options:key=123 3.创建关于gre流表 封装gre转发 ovs-ofctl add-flow br0 ip,in_port=1,nw_dst=10.10.0.0/16,actions=output:1001 解封gre转发 ovs-ofctl add-flow br0 in_port=1001,actions=output:1 vxlan 1.创建一个vxlan接口,并且指定端口id=2001 ovs-vsctl add-port br0 vxlan1 -- set Interface vxlan1 type=vxlan options:remote_ip=1.1.1.1 ofport_request=2001 2.可选选项 将tos或者ttl在隧道上继承,将vni设置成123,UDP目的端为设置成8472(默认为4789) ovs-vsctl set Interface vxlan1 options:tos=inherit options:ttl=inherit options:key=123 options:dst_port=8472 3.创建关于vxlan流表 封装vxlan转发 ovs-ofctl add-flow br0 ip,in_port=1,nw_dst=10.10.0.0/16,actions=output:2001 解封vxlan转发 ovs-ofctl add-flow br0 in_port=2001,actions=output:1 sflow配置 1.对网桥br0进行sflow监控 agent: 与collector通信所在的网口名,通常为管理口 target: collector监听的IP地址和端口,端口默认为6343 header: sFlow在采样时截取报文头的长度 polling: 采样时间间隔,单位为秒 ovs-vsctl -- --id=@sflow create sflow agent=eth0 target=\"10.0.0.1:6343\" header=128 sampling=64 polling=10 -- set bridge br0 sflow=@sflow 2.查看创建的sflow ovs-vsctl list sflow 3.删除对应的网桥sflow配置,参数为sFlow UUID ovs-vsctl remove bridge br0 sflow 7b9b962e-fe09-407c-b224-5d37d9c1f2b3 4.删除网桥下所有sflow配置 ovs-vsctl -- clear bridge br0 sflow 1 QoS配置 ingress policing 1.配置ingress policing,对接口eth0入流限速10Mbps ovs-vsctl set interface eth0 ingress_policing_rate=10000 ovs-vsctl set interface eth0 ingress_policing_burst=8000 2.清除相应接口的ingress policer配置 ovs-vsctl set interface eth0 ingress_policing_rate=0 ovs-vsctl set interface eth0 ingress_policing_burst=0 3.查看接口ingress policer配置 ovs-vsctl list interface eth0 4.查看网桥支持的Qos类型 ovs-appctl qos/show-types br0 端口镜像配置 1.配置eth0收到/发送的数据包镜像到eth1 ovs-vsctl -- set bridge br0 mirrors=@m \ -- --id=@eth0 get port eth0 \ -- --id=@eth1 get port eth1 \ -- --id=@m create mirror name=mymirror select-dst-port=@eth0 select-src-port=@eth0 output-port=@eth1 2.删除端口镜像配置 ovs-vsctl -- --id=@m get mirror mymirror -- remove bridge br0 mirrors @m 3.清除网桥下所有端口镜像配置 ovs-vsctl clear bridge br0 mirrors 4.查看端口镜像配置 ovs-vsctl get bridge br0 mirrors Open vSwitch中有多个命令,分别有不同的作用,大致如下: ovs-vsctl用于控制ovs db ovs-ofctl用于管理OpenFlow switch 的 flow ovs-dpctl用于管理ovs的datapath ovs-appctl用于查询和管理ovs daemon 转载于:https://www.cnblogs.com/liuhongru/p/10336849.html 本篇文章为转载内容。原文链接:https://blog.csdn.net/weixin_30876945/article/details/99916308。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-06-08 17:13:19
294
转载
转载文章
...成,一是前台的可视化操作,二是后台的数据库管理。网管对前台的管理和维护工作包括保障网络链路通畅、处理MIS终端的突发事件以及对操作员的管理、培训等,这是网管们日常做得最多、最辛苦的功课;然而MIS系统架构中同等重要的针对数据库的管理、维护和优化工作,现实中似乎并没有得到网管朋友的足够重视,看起来这都是程序员的事,事实上,一个网管如果能在MIS设计期间就数据表的规范化、表索引优化、容量设计、事务处理等诸多方面与程序员进行卓有成效的沟通和协作,那么日常的前台管理工作将会变得大为轻松,因为在某种意义上,数据库管理系统就相当于操作系统,在系统中占有同样重要的位置。 这正是SQL SERVER等数据库管理系统和dBASEX、ACCESS等数据库文件系统的本质区别,所以,对数据库管理系统操作能力的强弱在某种程度上也折射出了网管的水平——个人认为,称得上优秀的Admin,至少应该是一个称职的DBA(数据库管理员)。 下面以SQL SERVER(下称 SQLS)为例,将数据库管理中难于理解的“索引原理”问题给各位朋友作一个深入浅出的介绍。其他的数据库管理系统如Oracle、Sybase等,朋友们可以融会贯通,举一反三。 一、数据表的基本结构 建立数据库的目的是管理大量数据,而建立索引的目的就是提高数据检索效率,改善数据库工作性能,提高数据访问速度。对于索引,我们要知其然,更要知其所以然,关键在于认识索引的工作原理,才能更好的管理索引。 为认识索引工作原理,首先有必要对数据表的基本结构作一次全面的复习。 SQLS当一个新表被创建之时,系统将在磁盘中分配一段以8K为单位的连续空间,当字段的值从内存写入磁盘时,就在这一既定空间随机保存,当一个8K用完的时候,SQLS指针会自动分配一个8K的空间。这里,每个8K空间被称为一个数据页(Page),又名页面或数据页面,并分配从0-7的页号,每个文件的第0页记录引导信息,叫文件头(File header);每8个数据页(64K)的组合形成扩展区(Extent),称为扩展。全部数据页的组合形成堆(Heap)。 SQLS规定行不能跨越数据页,所以,每行记录的最大数据量只能为8K。这就是char和varchar这两种字符串类型容量要限制在8K以内的原因,存储超过8K的数据应使用text类型,实际上,text类型的字段值不能直接录入和保存,它只是存储一个指针,指向由若干8K的文本数据页所组成的扩展区,真正的数据正是放在这些数据页中。 页面有空间页面和数据页面之分。 当一个扩展区的8个数据页中既包含了空间页面又包括了数据或索引页面时,称为混合扩展(Mixed Extent),每张表都以混合扩展开始;反之,称为一致扩展(Uniform Extent),专门保存数据及索引信息。 表被创建之时,SQLS在混合扩展中为其分配至少一个数据页面,随着数据量的增长,SQLS可即时在混合扩展中分配出7个页面,当数据超过8个页面时,则从一致扩展中分配数据页面。 空间页面专门负责数据空间的分配和管理,包括:PFS页面(Page free space):记录一个页面是否已分配、位于混合扩展还是一致扩展以及页面上还有多少可用空间等信息;GAM页面(Global allocation map)和SGAM页面(Secodary global allocation map):用来记录空闲的扩展或含有空闲页面的混合扩展的位置。SQLS综合利用这三种类型的页面文件在必要时为数据表创建新空间; 数据页或索引页则专门保存数据及索引信息,SQLS使用4种类型的数据页面来管理表或索引:它们是IAM页、数据页、文本/图像页和索引页。 在WINDOWS中,我们对文件执行的每一步操作,在磁盘上的物理位置只有系统(system)才知道;SQL SERVER沿袭了这种工作方式,在插入数据的过程中,不但每个字段值在数据页面中的保存位置是随机的,而且每个数据页面在“堆”中的排列位置也只有系统(system)才知道。 这是为什么呢?众所周知,OS之所以能管理DISK,是因为在系统启动时首先加载了文件分配表:FAT(File Allocation Table),正是由它管理文件系统并记录对文件的一切操作,系统才得以正常运行;同理,作为管理系统级的SQL SERVER,也有这样一张类似FAT的表存在,它就是索引分布映像页:IAM(Index Allocation Map)。 IAM的存在,使SQLS对数据表的物理管理有了可能。 IAM页从混合扩展中分配,记录了8个初始页面的位置和该扩展区的位置,每个IAM页面能管理512,000个数据页面,如果数据量太大,SQLS也可以增加更多的IAM页,可以位于文件的任何位置。第一个IAM页被称为FirstIAM,其中记录了以后的IAM页的位置。 数据页和文本/图像页互反,前者保存非文本/图像类型的数据,因为它们都不超过8K的容量,后者则只保存超过8K容量的文本或图像类型数据。而索引页顾名思义,保存的是与索引结构相关的数据信息。了解页面的问题有助我们下一步准确理解SQLS维护索引的方式,如页拆分、填充因子等。 二、索引的基本概念 索引是一种特殊类型的数据库对象,它与表有着密切的联系。 索引是为检索而存在的。如一些书籍的末尾就专门附有索引,指明了某个关键字在正文中的出现的页码位置,方便我们查找,但大多数的书籍只有目录,目录不是索引,只是书中内容的排序,并不提供真正的检索功能。可见建立索引要单独占用空间;索引也并不是必须要建立的,它们只是为更好、更快的检索和定位关键字而存在。 再进一步说,我们要在图书馆中查阅图书,该怎么办呢?图书馆的前台有很多叫做索引卡片柜的小柜子,里面分了若干的类别供我们检索图书,比如你可以用书名的笔画顺序或者拼音顺序作为查找的依据,你还可以从作者名的笔画顺序或拼音顺序去查询想要的图书,反正有许多检索方式,但有一点很明白,书库中的书并没有按照这些卡片柜中的顺序排列——虽然理论上可以这样做,事实上,所有图书的脊背上都人工的粘贴了一个特定的编号①,它们是以这个顺序在排列。索引卡片中并没有指明这本书摆放在书库中的第几个书架的第几本,仅仅指明了这个特定的编号。管理员则根据这一编号将请求的图书返回到读者手中。这是很形象的例子,以下的讲解将会反复用到它。 SQLS在安装完成之后,安装程序会自动创建master、model、tempdb等几个特殊的系统数据库,其中master是SQLS的主数据库,用于保存和管理其它系统数据库、用户数据库以及SQLS的系统信息,它在SQLS中的地位与WINDOWS下的注册表相当。 master中有一个名为sysindexes的系统表,专门管理索引。SQLS查询数据表的操作都必须用到它,毫无疑义,它是本文主角之一。 查看一张表的索引属性,可以在查询分析器中使用以下命令:select from sysindexes where id=object_id(‘tablename’) ;而要查看表的索引所占空间的大小,可以使用系统存储过程命令:sp_spaceused tablename,其中参数tablename为被索引的表名。 三、平衡树 如果你通过书后的索引知道了一个关键字所在的页码,你有可能通过随机的翻寻,最终到达正确的页码。但更科学更快捷的方法是:首先把书翻到大概二分之一的位置,如果要找的页码比该页的页码小,就把书向前翻到四分之一处,否则,就把书向后翻到四分之三的地方,依此类推,把书页续分成更小的部分,直至正确的页码。这叫“两分法”,微软在官方教程MOC里另有一种说法:叫B树(B-Tree,Balance Tree),即平衡树。 一个表索引由若干页面组成,这些页面构成了一个树形结构。B树由“根”(root)开始,称为根级节点,它通过指向另外两个页,把一个表的记录从逻辑上分成两个部分:“枝”—--非叶级节点(Non-Leaf Level);而非叶级节点又分别指向更小的部分:“叶”——叶级节点(Leaf Level)。根节点、非叶级节点和叶级节点都位于索引页中,统称为索引节点,属于索引页的范筹。这些“枝”、“叶”最终指向了具体的数据页(Page)。在根级节点和叶级节点之间的叶又叫数据中间页。 “根”(root)对应了sysindexes表的Root字段,其中记载了非叶级节点的物理位置(即指针);非叶级节点位于根节点和叶节点之间,记载了指向叶级节点的指针;而叶级节点则最终指向数据页。这就是“平衡树”。 四、聚集索引和非聚集索引 从形式上而言,索引分为聚集索引(Clustered Indexes)和非聚集索引(NonClustered Indexes)。 聚集索引相当于书籍脊背上那个特定的编号。如果对一张表建立了聚集索引,其索引页中就包含着建立索引的列的值(下称索引键值),那么表中的记录将按照该索引键值进行排序。比如,我们如果在“姓名”这一字段上建立了聚集索引,则表中的记录将按照姓名进行排列;如果建立了聚集索引的列是数值类型的,那么记录将按照该键值的数值大小来进行排列。 非聚集索引用于指定数据的逻辑顺序,也就是说,表中的数据并没有按照索引键值指定的顺序排列,而仍然按照插入记录时的顺序存放。其索引页中包含着索引键值和它所指向该行记录在数据页中的物理位置,叫做行定位符(RID:Row ID)。好似书后面的的索引表,索引表中的顺序与实际的页码顺序也是不一致的。而且一本书也许有多个索引。比如主题索引和作者索引。 SQL Server在默认的情况下建立的索引是非聚集索引,由于非聚集索引不对表中的数据进行重组,而只是存储索引键值并用一个指针指向数据所在的页面。一个表如果没有聚集索引时,理论上可以建立249个非聚集索引。每个非聚集索引提供访问数据的不同排序顺序。 五、数据是怎样被访问的 若能真正理解了以上索引的基础知识,那么再回头来看索引的工作原理就简单和轻松多了。 (一)SQLS怎样访问没有建立任何索引数据表: Heap译成汉语叫做“堆”,其本义暗含杂乱无章、无序的意思,前面提到数据值被写进数据页时,由于每一行记录之间并没地有特定的排列顺序,所以行与行的顺序就是随机无序的,当然表中的数据页也就是无序的了,而表中所有数据页就形成了“堆”,可以说,一张没有索引的数据表,就像一个只有书柜而没有索引卡片柜的图书馆,书库里面塞满了一堆乱七八糟的图书。当读者对管理员提交查询请求后,管理员就一头钻进书库,对照查找内容从头开始一架一柜的逐本查找,运气好的话,在第一个书架的第一本书就找到了,运气不好的话,要到最后一个书架的最后一本书才找到。 SQLS在接到查询请求的时候,首先会分析sysindexes表中一个叫做索引标志符(INDID: Index ID)的字段的值,如果该值为0,表示这是一张数据表而不是索引表,SQLS就会使用sysindexes表的另一个字段——也就是在前面提到过的FirstIAM值中找到该表的IAM页链——也就是所有数据页集合。 这就是对一个没有建立索引的数据表进行数据查找的方式,是不是很没效率?对于没有索引的表,对于一“堆”这样的记录,SQLS也只能这样做,而且更没劲的是,即使在第一行就找到了被查询的记录,SQLS仍然要从头到尾的将表扫描一次。这种查询称为“遍历”,又叫“表扫描”。 可见没有建立索引的数据表照样可以运行,不过这种方法对于小规模的表来说没有什么太大的问题,但要查询海量的数据效率就太低了。 (二)SQLS怎样访问建立了非聚集索引的数据表: 如前所述,非聚集索引可以建多个,具有B树结构,其叶级节点不包含数据页,只包含索引行。假定一个表中只有非聚集索引,则每个索引行包含了非聚集索引键值以及行定位符(ROW ID,RID),他们指向具有该键值的数据行。每一个RID由文件ID、页编号和在页中行的编号组成。 当INDID的值在2-250之间时,意味着表中存在非聚集索引页。此时,SQLS调用ROOT字段的值指向非聚集索引B树的ROOT,在其中查找与被查询最相近的值,根据这个值找到在非叶级节点中的页号,然后顺藤摸瓜,在叶级节点相应的页面中找到该值的RID,最后根据这个RID在Heap中定位所在的页和行并返回到查询端。 例如:假定在Lastname上建立了非聚集索引,则执行Select From Member Where Lastname=’Ota’时,查询过程是:①SQLS查询INDID值为2;②立即从根出发,在非叶级节点中定位最接近Ota的值“Martin”,并查到其位于叶级页面的第61页;③仅在叶级页面的第61页的Martin下搜寻Ota的RID,其RID显示为N∶706∶4,表示Lastname字段中名为Ota的记录位于堆的第707页的第4行,N表示文件的ID值,与数据无关;④根据上述信息,SQLS立马在堆的第 707页第4行将该记录“揪”出来并显示于前台(客户端)。视表的数据量大小,整个查询过程费时从百分之几毫秒到数毫秒不等。 在谈到索引基本概念的时候,我们就提到了这种方式: 图书馆的前台有很多索引卡片柜,里面分了若干的类别,诸如按照书名笔画或拼音顺序、作者笔画或拼音顺序等等,但不同之处有二:① 索引卡片上记录了每本书摆放的具体位置——位于某柜某架的第几本——而不是“特殊编号”;② 书脊上并没有那个“特殊编号”。管理员在索引柜中查到所需图书的具体位置(RID)后,根据RID直接在书库中的具体位置将书提出来。 显然,这种查询方式效率很高,但资源占用极大,因为书库中书的位置随时在发生变化,必然要求管理员花费额外的精力和时间随时做好索引更新。 (三)SQLS怎样访问建立了聚集索引的数据表: 在聚集索引中,数据所在的数据页是叶级,索引数据所在的索引页是非叶级。 查询原理和上述对非聚集索引的查询相似,但由于记录是按照聚集索引中索引键值进行排序,换句话说,聚集索引的索引键值也就是具体的数据页。 这就好比书库中的书就是按照书名的拼音在排序,而且也只按照这一种排序方式建立相应的索引卡片,于是查询起来要比上述只建立非聚集索引的方式要简单得多。仍以上面的查询为例: 假定在Lastname字段上建立了聚集索引,则执行Select From Member Where Lastname=’Ota’时,查询过程是:①SQLS查询INDID值为1,这是在系统中只建立了聚集索引的标志;②立即从根出发,在非叶级节点中定位最接近Ota的值“Martin”,并查到其位于叶级页面的第120页;③在位于叶级页面第120页的Martin下搜寻到Ota条目,而这一条目已是数据记录本身;④将该记录返回客户端。 这一次的效率比第二种方法更高,以致于看起来更美,然而它最大的优点也恰好是它最大的缺点——由于同一张表中同时只能按照一种顺序排列,所以在任何一种数据表中的聚集索引只能建立一个;并且建立聚集索引需要至少相当于源表120%的附加空间,以存放源表的副本和索引中间页! 难道鱼和熊掌就不能兼顾了吗?办法是有的。 (四)SQLS怎样访问既有聚集索引、又有非聚集索引的数据表: 如果我们在建立非聚集索引之前先建立了聚集索引的话,那么非聚集索引就可以使用聚集索引的关键字进行检索,就像在图书馆中,前台卡片柜中的可以有不同类别的图书索引卡,然而每张卡片上都载明了那个特殊编号——并不是书籍存放的具体位置。这样在最大程度上既照顾了数据检索的快捷性,又使索引的日常维护变得更加可行,这是最为科学的检索方法。 也就是说,在只建立了非聚集索引的情况下,每个叶级节点指明了记录的行定位符(RID);而在既有聚集索引又有非聚集索引的情况下,每个叶级节点所指向的是该聚集索引的索引键值,即数据记录本身。 假设聚集索引建立在Lastname上,而非聚集索引建立在Firstname上,当执行Select From Member Where Firstname=’Mike’时,查询过程是:①SQLS查询INDID值为2;②立即从根出发,在Firstname的非聚集索引的非叶级节点中定位最接近Mike的值“Jose”条目;③从Jose条目下的叶级页面中查到Mike逻辑位置——不是RID而是聚集索引的指针;④根据这一指针所指示位置,直接进入位于Lastname的聚集索引中的叶级页面中到达Mike数据记录本身;⑤将该记录返回客户端。 这就完全和我们在“索引的基本概念”中讲到的现实场景完全一样了,当数据发生更新的时候,SQLS只负责对聚集索引的健值驾以维护,而不必考虑非聚集索引,只要我们在ID类的字段上建立聚集索引,而在其它经常需要查询的字段上建立非聚集索引,通过这种科学的、有针对性的在一张表上分别建立聚集索引和非聚集索引的方法,我们既享受了索引带来的灵活与快捷,又相对规避了维护索引所导致的大量的额外资源消耗。 六、索引的优点和不足 索引有一些先天不足:1:建立索引,系统要占用大约为表的1.2倍的硬盘和内存空间来保存索引。2:更新数据的时候,系统必须要有额外的时间来同时对索引进行更新,以维持数据和索引的一致性——这就如同图书馆要有专门的位置来摆放索引柜,并且每当库存图书发生变化时都需要有人将索引卡片重整以保持索引与库存的一致。 当然建立索引的优点也是显而易见的:在海量数据的情况下,如果合理的建立了索引,则会大大加强SQLS执行查询、对结果进行排序、分组的操作效率。 实践表明,不恰当的索引不但于事无补,反而会降低系统性能。因为大量的索引在进行插入、修改和删除操作时比没有索引花费更多的系统时间。比如在如下字段建立索引应该是不恰当的:1、很少或从不引用的字段;2、逻辑型的字段,如男或女(是或否)等。 综上所述,提高查询效率是以消耗一定的系统资源为代价的,索引不能盲目的建立,必须要有统筹的规划,一定要在“加快查询速度”与“降低修改速度”之间做好平衡,有得必有失,此消则彼长。这是考验一个DBA是否优秀的很重要的指标。 至此,我们一直在说SQLS在维护索引时要消耗系统资源,那么SQLS维护索引时究竟消耗了什么资源?会产生哪些问题?究竟应该才能优化字段的索引? 在上篇中,我们就索引的基本概念和数据查询原理作了详细阐述,知道了建立索引时一定要在“加快查询速度”与“降低修改速度”之间做好平衡,有得必有失,此消则彼长。那么,SQLS维护索引时究竟怎样消耗资源?应该从哪些方面对索引进行管理与优化?以下就从七个方面来回答这些问题。 一、页分裂 微软MOC教导我们:当一个数据页达到了8K容量,如果此时发生插入或更新数据的操作,将导致页的分裂(又名页拆分): 1、有聚集索引的情况下:聚集索引将被插入和更新的行指向特定的页,该页由聚集索引关键字决定; 2、只有堆的情况下:只要有空间就可以插入新的行,但是如果我们对行数据的更新需要更多的空间,以致大于了当前页的可用空间,行就被移到新的页中,并且在原位置留下一个转发指针,指向被移动的新行,如果具有转发指针的行又被移动了,那么原来的指针将重新指向新的位置; 3、如果堆中有非聚集索引,那么尽管插入和更新操作在堆中不会发生页分裂,但是在非聚集索引上仍然产生页分裂。 无论有无索引,大约一半的数据将保留在老页面,而另一半将放入新页面,并且新页面可能被分配到任何可用的页。所以,频繁页分裂,后果很严重,将使物理表产生大量数据碎片,导致直接造成I/O效率的急剧下降,最后,停止SQLS的运行并重建索引将是我们的唯一选择! 二、填充因子 然而在“混沌之初”,就可以在一定程度上避免不愉快出现:在创建索引时,可以为这个索引指定一个填充因子,以便在索引的每个叶级页面上保留一定百分比的空间,将来数据可以进行扩充和减少页分裂。填充因子是从0到100的百分比数值,设为100时表示将数据页填满。只有当不会对数据进行更改时(例如只读表中)才用此设置。值越小则数据页上的空闲空间越大,这样可以减少在索引增长过程中进行页分裂的需要,但这一操作需要占用更多的硬盘空间。 填充因子只在创建索引时执行,索引创建以后,当表中进行数据的添加、删除或更新时,是不会保持填充因子的,如果想在数据页上保持额外的空间,则有悖于使用填充因子的本意,因为随着数据的输入,SQLS必须在每个页上进行页拆分,以保持填充因子指定的空闲空间。因此,只有在表中的数据进行了较大的变动,才可以填充数据页的空闲空间。这时,可以从容的重建索引,重新指定填充因子,重新分布数据。 反之,填充因子指定不当,就会降低数据库的读取性能,其降低量与填充因子设置值成反比。例如,当填充因子的值为50时,数据库的读取性能会降低两倍!所以,只有在表中根据现有数据创建新索引,并且可以预见将来会对这些数据进行哪些更改时,设置填充因子才有意义。 三、两道数学题 假定数据库设计没有问题,那么是否象上篇中分析的那样,当你建立了众多的索引,在查询工作中SQLS就只能按照“最高指示”用索引处理每一个提交的查询呢?答案是否定的! 上篇“数据是怎样被访问的”章节中提到的四种索引方案只是一种静态的、标准的和理论上的分析比较,实际上,将在外,军令有所不从,SQLS几乎完全是“自主”的决定是否使用索引或使用哪一个索引! 这是怎么回事呢? 让我们先来算一道题:如果某表的一条记录在磁盘上占用1000字节(1K)的话,我们对其中10字节的一个字段建立索引,那么该记录对应的索引大小只有10字节(0.01K)。上篇说过,SQLS的最小空间分配单元是“页(Page)”,一个页面在磁盘上占用8K空间,所以一页只能存储8条“记录”,但可以存储800条“索引”。现在我们要从一个有8000条记录的表中检索符合某个条件的记录(有Where子句),如果没有索引的话,我们需要遍历8000条×1000字节/8K字节=1000个页面才能够找到结果。如果在检索字段上有上述索引的话,那么我们可以在8000条×10字节/8K字节=10个页面中就检索到满足条件的索引块,然后根据索引块上的指针逐一找到结果数据块,这样I/O访问量肯定要少得多。 然而有时用索引还不如不用索引快! 同上,如果要无条件检索全部记录(不用Where子句),不用索引的话,需要访问8000条×1000字节/8K字节=1000个页面;而使用索引的话,首先检索索引,访问8000条×10字节/8K字节=10个页面得到索引检索结果,再根据索引检索结果去对应数据页面,由于是检索全部数据,所以需要再访问8000条×1000字节/8K字节=1000个页面将全部数据读取出来,一共访问了1010个页面,这显然不如不用索引快。 SQLS内部有一套完整的数据索引优化技术,在上述情况下,SQLS会自动使用表扫描的方式检索数据而不会使用任何索引。那么SQLS是怎么知道什么时候用索引,什么时候不用索引的呢?因为SQLS除了维护数据信息外,还维护着数据统计信息! 四、统计信息 打开企业管理器,单击“Database”节点,右击Northwind数据库→单击“属性”→选择“Options”选项卡,观察“Settings”下的各项复选项,你发现了什么? 从Settings中我们可以看到,在数据库中,SQLS将默认的自动创建和更新统计信息,这些统计信息包括数据密度和分布信息,正是它们帮助SQLS确定最佳的查询策略:建立查询计划和是否使用索引以及使用什么样的索引。 在创建索引时,SQLS会创建分布数据页来存放有关索引的两种统计信息:分布表和密度表。查询优化器使用这些统计信息估算使用该索引进行查询的成本(Cost),并在此基础上判断该索引对某个特定查询是否有用。 随着表中的数据发生变化,SQLS自动定期更新这些统计信息。采样是在各个数据页上随机进行。从磁盘读取一个数据页后,该数据页上的所有行都被用来更新统计信息。统计信息更新的频率取决于字段或索引中的数据量以及数据更改量。比如,对于有一万条记录的表,当1000个索引键值发生改变时,该表的统计信息便可能需要更新,因为1000 个值在该表中占了10%,这是一个很大的比例。而对于有1千万条记录的表来说,1000个索引值发生更改的意义则可以忽略不计,因此统计信息就不会自动更新。 至于它们帮助SQLS建立查询计划的具体过程,限于篇幅,这里就省略了,请有兴趣的朋友们自己研究。 顺便多说一句,SQLS除了能自动记录统计信息之外,还可以记录服务器中所发生的其它活动的详细信息,包括I/O 统计信息、CPU 统计信息、锁定请求、T-SQL 和 RPC 统计信息、索引和表扫描、警告和引发的错误、数据库对象的创建/除去、连接/断开、存储过程操作、游标操作等等。这些信息的读取、设置请朋友们在SQLS联机帮助文档(SQL Server Books Online)中搜索字符串“Profiler”查找。 五、索引的人工维护 上面讲到,某些不合适的索引将影响到SQLS的性能,随着应用系统的运行,数据不断地发生变化,当数据变化达到某一个程度时将会影响到索引的使用。这时需要用户自己来维护索引。 随着数据行的插入、删除和数据页的分裂,有些索引页可能只包含几页数据,另外应用在执行大量I/O的时候,重建非聚聚集索引可以维护I/O的效率。重建索引实质上是重新组织B树。需要重建索引的情况有: 1) 数据和使用模式大幅度变化; 2)排序的顺序发生改变; 3)要进行大量插入操作或已经完成; 4)使用I/O查询的磁盘读次数比预料的要多; 5)由于大量数据修改,使得数据页和索引页没有充分使用而导致空间的使用超出估算; 6)dbcc检查出索引有问题。 六、索引的使用原则 接近尾声的时候,让我们再从另一个角度认识索引的两个重要属性----唯一性索引和复合性索引。 在设计表的时候,可以对字段值进行某些限制,比如可以对字段进行主键约束或唯一性约束。 主键约束是指定某个或多个字段不允许重复,用于防止表中出现两条完全相同的记录,这样的字段称为主键,每张表都可以建立并且只能建立一个主键,构成主键的字段不允许空值。例如职员表中“身份证号”字段或成绩表中“学号、课程编号”字段组合。 而唯一性约束与主键约束类似,区别只在于构成唯一性约束的字段允许出现空值。 建立在主键约束和唯一性约束上的索引,由于其字段值具有唯一性,于是我们将这种索引叫做“唯一性索引”,如果这个唯一性索引是由两个以上字段的组合建立的,那么它又叫“复合性索引”。 注意,唯一索引不是聚集索引,如果对一个字段建立了唯一索引,你仅仅不能向这个字段输入重复的值。并不妨碍你可以对其它类型的字段也建立一个唯一性索引,它们可以是聚集的,也可以是非聚集的。 唯一性索引保证在索引列中的全部数据是唯一的,不会包含冗余数据。如果表中已经有一个主键约束或者唯一性约束,那么当创建表或者修改表时,SQLS自动创建一个唯一性索引。但出于必须保证唯一性,那么应该创建主键约束或者唯一性键约束,而不是创建一个唯一性索引。当创建唯一性索引时,应该认真考虑这些规则:当在表中创建主键约束或者唯一性键约束时, SQLS钭自动创建一个唯一性索引;如果表中已经包含有数据,那么当创建索引时,SQLS检查表中已有数据的冗余性,如果发现冗余值,那么SQLS就取消该语句的执行,并且返回一个错误消息,确保表中的每一行数据都有一个唯一值。 复合索引就是一个索引创建在两个列或者多个列上。在搜索时,当两个或者多个列作为一个关键值时,最好在这些列上创建复合索引。当创建复合索引时,应该考虑这些规则:最多可以把16个列合并成一个单独的复合索引,构成复合索引的列的总长度不能超过900字节,也就是说复合列的长度不能太长;在复合索引中,所有的列必须来自同一个表中,不能跨表建立复合列;在复合索引中,列的排列顺序是非常重要的,原则上,应该首先定义最唯一的列,例如在(COL1,COL2)上的索引与在(COL2,COL1)上的索引是不相同的,因为两个索引的列的顺序不同;为了使查询优化器使用复合索引,查询语句中的WHERE子句必须参考复合索引中第一个列;当表中有多个关键列时,复合索引是非常有用的;使用复合索引可以提高查询性能,减少在一个表中所创建的索引数量。 综上所述,我们总结了如下索引使用原则: 1)逻辑主键使用唯一的成组索引,对系统键(作为存储过程)采用唯一的非成组索引,对任何外键列采用非成组索引。考虑数据库的空间有多大,表如何进行访问,还有这些访问是否主要用作读写。 2)不要索引memo/note 字段,不要索引大型字段(有很多字符),这样作会让索引占用太多的存储空间。 3)不要索引常用的小型表 4)一般不要为小型数据表设置过多的索引,假如它们经常有插入和删除操作就更别这样作了,SQLS对这些插入和删除操作提供的索引维护可能比扫描表空间消耗更多的时间。 七、大结局 查询是一个物理过程,表面上是SQLS在东跑西跑,其实真正大部分压马路的工作是由磁盘输入输出系统(I/O)完成,全表扫描需要从磁盘上读表的每一个数据页,如果有索引指向数据值,则I/O读几次磁盘就可以了。但是,在随时发生的增、删、改操作中,索引的存在会大大增加工作量,因此,合理的索引设计是建立在对各种查询的分析和预测上的,只有正确地使索引与程序结合起来,才能产生最佳的优化方案。 一般来说建立索引的思路是: (1)主键时常作为where子句的条件,应在表的主键列上建立聚聚集索引,尤其当经常用它作为连接的时候。 (2)有大量重复值且经常有范围查询和排序、分组发生的列,或者非常频繁地被访问的列,可考虑建立聚聚集索引。 (3)经常同时存取多列,且每列都含有重复值可考虑建立复合索引来覆盖一个或一组查询,并把查询引用最频繁的列作为前导列,如果可能尽量使关键查询形成覆盖查询。 (4)如果知道索引键的所有值都是唯一的,那么确保把索引定义成唯一索引。 (5)在一个经常做插入操作的表上建索引时,使用fillfactor(填充因子)来减少页分裂,同时提高并发度降低死锁的发生。如果在只读表上建索引,则可以把fillfactor置为100。 (6)在选择索引字段时,尽量选择那些小数据类型的字段作为索引键,以使每个索引页能够容纳尽可能多的索引键和指针,通过这种方式,可使一个查询必须遍历的索引页面降到最小。此外,尽可能地使用整数为键值,因为它能够提供比任何数据类型都快的访问速度。 SQLS是一个很复杂的系统,让索引以及查询背后的东西真相大白,可以帮助我们更为深刻的了解我们的系统。一句话,索引就象盐,少则无味多则咸。 本篇文章为转载内容。原文链接:https://blog.csdn.net/qq_28052907/article/details/75194926。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-04-30 23:10:07
97
转载
转载文章
...发团队。但是如果关注数据的话,就会发现超过70%的工作是仅仅靠五个人来完成的。 Log4j的主页上展示了十几位项目团队的成员。而大多项目的开发人员要比其原本需要的少得多----这是高度依赖开发人员团队所呈现出来的问题。 “如今几乎没有人愿意为现有的开源项目作出贡献”,来自DNS网络公司NS1的杰出工程师Jeremy Strech说,“因为通常来说,这没有直接的物质回报,也很少提供荣誉----大多数用户甚至不知道他们所用的软件是谁维护的。” 他说,开源贡献者们最常见的动机就是添加他们自己想要的功能。“一旦实现了这一点,他们几乎都不会留下来。” 与此同时,随着项目的逐渐火爆,对于维护方面的核心团队来说,他们的负担也在不断增加。 “更多的用户意味有着更多的功能需求和错误报告----但不是更多的维护人员”,Stretch说。“曾经令人愉快的爱好很快就会变成一项乏味的项目,所以很多维护人员选择干脆完全放弃他们的项目,这也是可以理解的。” Part1公地悲剧 开源软件的生态系统,就是“公地悲剧”的一个完美例子。 这个悲剧就是---当一种资源,无论是一个超限的公园还是一个开源项目,所有人都在使用而没有人贡献之时,最终都会因为过度使用和投入不足而崩溃坍塌。 这种方式可以在短期内为你节省资金,但随着时间的推移,它可能会变成项目里致命的缺陷。 拿Linux来说,这个开源操作系统在全球前100万台服务器中运行率在96%以上,且这些服务器90%的云基础设施也都在Linux上。更不用说世界上85%的智能手机都运行着Linux,即Android操作系统。 这些常见开源项目的列表还在逐渐增加着。 所以没有开源,今天的大部分技术基础设施的建设也将会戛然而止。 “这是一个很现实的问题”,Data.org的执行董事Danil Mikhailov说,该组织是由万事达包容性发展中心和洛克菲勒基金会支持,旨在促进使用数据科学来应对当今社会所面临的巨大挑战的非营利性组织。 虽然几乎所有组织都在使用着开源软件,但只有少数组织为这些项目作出了贡献。The New Stack、Linux Foundation Research 和 TODO Group 在 9 月发布的一项调查中,42% 的参与者表示,他们至少有时会为开源项目做出贡献。 而同一项研究表明,只有36%的组织会培训他们的工程师为开源作出贡献。 个体公司应该支持贡献这些他们使用最多且对他们成功至关重要的项目,Mikhailov认为:“如果你使用开源,你就应该为他做出属于你自己的贡献。” Part2OSPO的好处:更少的技术负债,更好的招聘效果 参与开源社区----特别是在内部开源计划办公室(OSPO)的指导下----不仅可以保证对组织成功至关重要项目的健康发展,还可以提高项目安全性,同时可以允许工程师在项目发展规划中起到更大的作用。 例如,如果一家公司使用了开源工具,并对其进行了一些调整使其变得更好。但如果这项改进没有反馈到开源社区,那么开源项目的正式版本就会一开始与该公司所使用的版本有所不同。 “当原始数据来源发生变化且你所使用的是不同的版本时,你的技术负债将越来越多。而这些差异是以天为单位迅速增长的。”VMware 开源营销和战略总监 Suzanne Ambiel 表示,“所以你很快就会变成一个开源项目里独一无二变体的‘自豪’用户和维护人员。” “如果技术负债越来越多,那么公司的管理成本则会非常昂贵”。 实际上对于开源活动的支持也变成了一种招聘途径。“这真是一块吸引人才的磁铁,”Ambiel说,“这也是新员工所寻求的“。 她还提到,一些工程经理可能会对贡献开源而减损核心产品的开发的精力而感到担忧。她补充到,他们的理由有可能是这样的:“我只有有限的才华与时间,且我需要这些只做我认为可以处理且看到投资回报的事情。” 但她说,这是一种鼠目寸光的态度。支持开源社区并且作出贡献的员工,可以从中培养技能与增长才干。 云安全供应商 Sysdig 的首席技术官兼创始人 Loris Degionni 也赞同这一观点:“找到为开源做出贡献的员工无疑就找到一座金矿,”他说。 他认为,这些参与开源的员工更具备公司想拥有的竞争力并将一些功能融入至社区所支持的标准中。且在人才争夺战中,拥抱开源的公司也更受到开发人员的青睐。 “最后,开源项目是由你可能无法聘请的技术专家社区推动的”,他说,“当员工积极参与并于这些专家合作时,他们将能更好地深入这些顶级的实践,并将这些收获带回到你的组织之中。” “当原始数据来源发生变化且你所使用的是不同的版本时,你的技术负债将越来越多...所以你很快就会变成一个开源项目里独一无二变体的”自豪“用户和维护人员。”— Suzanne Ambiel,VMware 开源营销和战略总监 “但是这一切终究不会白费--开发人员不应该把空闲时间用在磨练他们的技能上,因为你的公司很快就会在他们的努力中看到好处。” Degionni认为,OSPO(开源计划办公室)可以帮助公司实现这些目标,以及帮助确定贡献的优先级并确保合作的进行。除此之外,他们也可以对公司内部开发应用程序方面的治理提供相关帮助。 “开源团队的成员也可以成为开源技术的伟大内部传播者,并充当组织与更广泛社区之间的桥梁。”他补充道。 在 The New Stack、Linux Foundation Research 和 TODO Group 的 9 月调查中,近 53% 的拥有 OSPO的组织表示,由于拥有了OSPO,他们看到了更多创新,而近 43% 的组织表示,他们在外部开源项目的参与度上有所增加。 Part3更多OSPO的好处:商业优势 网络安全公司 ThreatX 的首席创新官 Tom Hickman 表示,为开源社区做出贡献,不仅有助于社区,还有助于为社区做出贡献的公司。 “围绕一个项目而发展的开发人员社区,有助于代码库的形成,并吸引更多的开发人员参与”,他说,“这可以变成一个良性循环。” 此外,根据哈佛商学院的研究,为开源项目作出贡献的公司从使用开源的项目中获得的生产价值,是不参与开源项目公司的两倍。 Cloud Native Computing Foundation 的首席技术官 Chris Aniszczyk 说,世界上许多巨头公司都为开源作出了贡献。他还提到,开源贡献者的指数是作为公司是否有所作为的参考。 科技巨头占据了这份榜单的主导地位:谷歌、微软、红帽、英特尔、IBM、亚马逊、Facebook、VMware、GitHub 和 SAP 依次是排名前 10 的贡献者。但Aniszczyk 表示,但也有很多终端用户公司进入前 100 名,包括 Uber、BBC、Orange、Netflix 和 Square。 “我们一直知道,在上游项目中工作不仅仅是关正确与否----它是开源软件开发的最佳方法,也是向客户提供开源福利的最佳方式”他说,“很高兴看到IT领导者们也认识到了这一点。” 为了和这些公司一起作出贡献,公司也需要有自己的开源策略,而拥有一个开源计划办公室则可以为其提供帮助。 “在使用开源软件方面,OPSO为公司提供了一个至关重要的能力中心”他说。 这与公司拥有安全运营中心的方式类似,他说。 “围绕一个项目而发展的开发人员社区,有助于代码库的形成,并吸引更多的开发人员参与,这可以变成一个良性循环。” ——Tom Hickman,ThreatX 首席创新官 “如果你对安全团队进行相应投资,你通常是不会期望你的软件是安全的,也无法及时应对安全事件。”他说。 “同样的逻辑也适用于 OSPO,这就是为什么你会看到许多领先的公司,例如Apple、Meta、Twitter、Goldman Sachs、Bloomberg 和 Google 都拥有 OSPO。他们走在了趋势的前面。” 而对组织内的开源活动的支持态度亦可成为软件供应商们的差异化原因与营销的机会。 根据Red Hat 2月分发布的一项调查,82%的IT领导者更倾向于选择为开源社区作出贡献的软件供应商。 受访者表示,当供应商支持开源社区时,就表示着他们更熟悉开源的流程并且在客户遇到技术难题时会更加有效。 但收益的不仅仅是软件供应商们。 根据 The New Stack、Linux Foundation Research 和 TODO Group 9 月份的调查,57% 拥有 OSPO 的组织将使用它们来进一步发展战略关系和建立合作伙伴关系。 十年前,Mark Hinkle 在 Citrix 工作时创办了一个开源计划办公室。他指出了在内部拥有一个 OSPO将如何使公司受益。 “对于我们来说,最大的工作是让不熟悉开源的员工学会并参与其中,成为优秀的社区成员”,他说,“我们还就如何确保我们的IP不会在没有正确理解的情况下进入项目的情况提供了指导,并确保我们没有与我们企业软件许可相冲突的开源项目合作。” 他说,OSPO还帮助Citrix确定了公司参与开源项目和Linux基金会等贸易组织的战略机会。 如今,他是云原生开源集成平台 TriggerMesh 的首席执行官兼联合创始人。 他说,参与开源系统对公司来说有着重大的经济效益。 “我们参与Knative是为了分享我们基础底层平台的开发,但作为业务的一部分,我们也拥有相关的增值服务。”他说,“通过共享该平台的研发,这为我们提供了更多的资源来改进我们自己的差异化技术。” Part4如何入门开源 在 The New Stack、Linux Foundation Research 和 TODO Group 的 9 月份调查中,有 63% 的公司表示,拥有OSPO 对其工程或产品团队的成功至关重要,高于上一年度该项研究数据的 54%。 其中77% 的人表示他们的开源程序对他们的软件实践产生了积极影响,例如提高了代码质量。 但公司也不可能总是为他们使用的每一个开源项目而花费精力。 “首先,节流一下”,VMware 的 Ambiel 建议道。 公司应该关注投入使用中最有意义的项目。而这也是OSPO可以帮助确定优先事项并确保技术与战略一致性的领域。 之后,开发人员应该自己去了解一下。项目通常提供相关在线文档,一般包含贡献着指南、治理文档和未解决问题列表。 “对于那些你较感兴趣的项目中,你可以介绍一下自己----打个招呼”,她说。“然后转到Slack频道或者分发列表,询问他们需要帮助的地方。也许他们不需要帮助,一切完好;又或者他们也有可能使用新人来审查核验代码。” Ambiel 说,开源计划办公室不仅可以帮助制定为开源社区做出贡献的商业案例,还可以帮助公司以安全、可靠和健全的方式来做这件事。 “如果我为一家公司工作,并想为开源做出贡献,我不想意外披露、泄露或破坏任何专利,”她说。“而OSPO可以帮助您做出明智的选择。” 她说,OSPO还可以在开源方面提供领导力和指导理念的支持。“它可以提供引领、指导、辅导和最佳实践的作用。” Aqua Security的开发人员倡导者Anaïs Urlichs则认为,支持开源的承诺必须从高层开始。 她说,“公司在多数时候往往不重视对开源的投资,所以员工自然而然不被鼓励对此作出贡献。” 在这些情况下,员工对于开源的热情也会在空闲时间里对开源的建设而消散殆尽,这对于开源的发展来说是不可持续的。 “如果公司对开源项目依赖度高,那么将开源贡献纳入工程师的日程安排是很重要的,”她说。“一些公司定义了员工可以为开源建设的时间百分比,将其作为他们正常工作日的一部分。” The New Stack 是 Insight Partners 的全资子公司,Insight Partners 是本文提到的以下公司的投资者:Sysdig、Aqua Security。 中英对照版 How an OSPO Can Help Your Engineers Give Back to Open Source OSPO (开源项目办公室)是如何使工程师回馈开源的 When it comes to open source software, there’s a big and growing problem: most organizations are takers, not givers. 谈到开源软件,有一个较大且日益严重的问题:大多数组织都是索取者,而不是给予者。 There’s a classic XKCD comic that shows a giant structure representing modern digital infrastructure, dependent on a tiny component created by “some random person in Nebraska” who has been “thanklessly maintaining since 2003.” 经典漫画XKCD展示了一个代表现代数字基础设施的巨大结构,它依赖于“内布拉斯加州的某位人士”创建的微小组件,该组件“自2003年来一直都处于吃力不讨好的状态”。 Randall Monroe’s XKCD comic illustrates the open source dilemma: overreliance on a small number of volunteer project maintainers. Randall Monroe 的XKCD漫画展示了目前开源面临的窘境:过度依赖少数项目维护志愿者的志愿服务。 This would have been funny, except that this is exactly what happened when security vulnerabilities were discovered in Log4j last December. (开源项目由志愿者自发来维护,)这听起来像是一件很滑稽的事情,但事实上去年十二月在Log4j中发现的安全漏洞也确实存在着上述情况。 The Java-based logging tool is ubiquitous in enterprise publications. In the last three months, for example, Log4j has been downloaded more than 30 million times, according to a report by the enterprise software company Sonatype. 然而这个基于Java的日志记录工具已经在企业内部刊物中无处不在。例如根据软件公司Sonatype的一份报告显示,在过去的三个月里,Log4j的下载量就已经超过3000万次。 The tool has 440,000 lines of code, according to Synopsys‘ Black Duck Open Hub research tool, with nearly 24,000 contributions by nearly 200 developers. That’s a large dev team compared to other open source projects. But looking closer at the numbers, more than 70% of commits were by just five people. 根据Synopsys(新思)公司旗下的Black Duck Open Hub 研究工具显示。Log4j有着440,000行代码,由近200名开发人员贡献了将近24,000行代码。其实与其他开源项目相比,这是一个庞大的开发团队。但是如果关注数据的话,就会发现超过70%的提交是仅仅靠五个人来完成的。 Log4j’s home page lists about a dozen members on its project team. Most projects have far fewer developers working on them — and that presents a problem for the organizations that depend on them. Log4j的主页上展示了十几位项目团队的成员。而大多项目的开发人员要比其原本需要的少得多----这是高度依赖开发人员团队所呈现出来的问题。 “There is little incentive for anyone today to contribute to an existing open source project,” said Jeremy Stretch, distinguished engineer at NS1, a DNS network company. “There’s usually no direct compensation, and few accolades are offered — most users don’t even know who maintains the software that they use.” “如今的人没有什么动力去为现有的开源项目做贡献”,来自DNS网络公司NS1的杰出工程师Jeremy Strech说,“因为通常来说,这没有直接的物质回报,也很少提供荣誉----大多数用户甚至不知道他们所用的软件是谁维护的。” The most common motivation among open source contributors is to add a feature that they themselves want to see, he said. “Once this has been achieved, the contributor rarely sticks around.” 他说,开源贡献者们最常见的动机就是添加他们自己想要的功能。“一旦实现了这一点,他们几乎都不会留下来。” Meanwhile, as a project becomes more popular, the burden on the core team of maintainers keeps increasing. 与此同时,随着项目的逐渐流行,对于维护方面的核心团队来说,他们的负担也在不断增加。 “More users means more feature requests and more bug reports — but not more maintainers,” Stretch said. “What was once an enjoyable hobby can quickly become a tedious chore, and many maintainers understandably opt to simply abandon their projects altogether.” “更多的用户意味有着更多的功能需求和错误报告----但不是更多的维护人员”,Stretch说。“曾经令人愉快的爱好很快就会变成一项乏味的项目,所以很多维护人员选择干脆完全放弃他们的项目,这也是可以理解的。” Part1The Tragedy of the Commons The open source software ecosystem is a perfect example of the “tragedy of the commons.” 开源软件的生态系统,就是“公地悲剧”的一个完美例子。 And the tragedy is — when everyone uses, but no one contributes, that resource — whether it’s an overrun park or an open source project — eventually collapses from overuse and underinvestment. Everyone loves using free stuff, but everyone expects someone else to take care of it. 这个悲剧就是---当一种资源,无论是一个超限的公园还是一个开源项目,所有人都在使用而没有人贡献之时,最终都会因为过度使用和投入不足而崩溃坍塌。 This approach can save you money in the short term, but it can become a fatal flaw over time. Especially since open source software is everywhere, running everything. 这种方式可以在短期内为你节省资金,但随着时间的推移,它可能会变成项目里致命的缺陷。 Linux, for example, the open source operating system, runs on 96% of the world’s top 1 million servers, and 90% of all cloud infrastructure is on Linux. Not to mention that 85% of all smartphones in the world run Linux, in the form of the Android OS. 拿Linux来说,这个开源操作系统在全球前100万台服务器中运行率在96%以上,且这些服务器90%的云基础设施也都在Linux上。更不用说世界上85%的智能手机都运行着Linux,即Android操作系统。 Then there’s Java, Apache, WordPress, Cassandra, Hadoop, MySQL, PHP, ElasticSearch, Kubernetes — the list of ubiquitous open source projects goes on and on. 还有Java, Apache, WordPress, Cassandra, Hadoop, MySQL, PHP, ElasticSearch, Kubernetes--这些常见开源项目的列表还在逐渐增加着。 Without open source, much of today’s technical infrastructure would immediately grind to a halt. 如果没有开源,今天的大部分技术基础设施的建设也将会戛然而止。 “It is a real problem,” said Danil Mikhailov, executive director at Data.org, a nonprofit backed by the Mastercard Center for Inclusive Growth and The Rockefeller Foundation that promotes the use of data science to tackle society’s greatest challenges. “这是一个很现实的问题”,Data.org的执行董事Danil Mikhailov说,该组织是由万事达包容性发展中心和洛克菲勒基金会支持,旨在促进使用数据科学来应对当今社会所面临的巨大挑战的非营利性组织。 While nearly all organizations use open source software, only a minority contribute to those projects. Forty-two percent of participants in a survey released in September by The New Stack, Linux Foundation Research, and the TODO Group said tthey contribute at least sometimes to open source projects. 虽然几乎所有组织都在使用着开源软件,但只有少数组织为这些项目作出了贡献。The New Stack、Linux Foundation Research 和 TODO Group 在 9 月发布的一项调查中,42% 的参与者表示,他们至少有时会为开源项目做出贡献。 The same study showed that only 36% of organizations train their engineers to contribute to open source. 而同一项研究表明,只有36%的组织会培训他们的工程师为开源作出贡献。 Individual companies should support projects that they use the most and are critical to their success, Mikhailov said: “If you use, you contribute.” 个体公司应该支持贡献这些他们使用最多且对他们成功至关重要的项目,Mikhailov认为:“如果你使用开源,你就应该为他做出属于你自己的贡献。” Part2OSPO Benefits:Less Tech Debt,Better Recruiting Participating in open source communities — especially when guided by an in-house open source program office (OSPO) — can help ensure the health of projects critical to your organization’s success, improve those projects’ security, and allow your engineers to have more impact in the projects’ development road map. 参与开源社区——特别是在内部开源项目办公室(OSPO)的指导下——不仅可以保证对组织成功至关重要项目的健康发展,还可以提高项目安全性,同时可以允许工程师在项目发展规划中起到更大的影响。 Say, for example, a company uses an open source tool and modifies it a little to make it better. If that improvement isn’t contributed back to the community, then the official version of the open source project will start to diverge from what the company is using 例如,如果一家公司使用了开源工具,并对其进行了一些调整使其变得更好。但如果这项改进没有反馈到开源社区,那么开源项目的正式版本就会一开始与该公司所使用的版本有所不同。 “You start to grow technical debt because when the original source changes and you’ve got a different version. Those differences grow rapidly, compounding daily. It doesn’t take long for you to be the proud user and maintainer of a one-of-a-kind open source project variant,” said Suzanne Ambiel, director, open source marketing and strategy at VMware. “当原始代码来源发生变化且你所使用的是不同的版本时,你的技术负债将越来越多。而这些差异是以天为单位迅速增长的。”VMware 开源营销和战略总监 Suzanne Ambiel 表示,“所以你很快就会变成一个开源项目里独一无二变体的‘自豪’用户和维护人员。” “The technical debt gets bigger and bigger and it gets very expensive for a company to manage.” “如果技术负债越来越多,那么公司的管理成本则会非常昂贵”。 Support for open source activity can also be a recruiting tool. “It’s really a talent magnet,” said Ambiel. “It’s one of the things that new hires look for.” 实际上对于开源活动的支持也变成了一种招聘途径。“这真是一块吸引人才的磁铁,”Ambiel说,“这也是新员工所寻求的“。 Some engineering managers might worry that open source contributions will detract from core product development, she said. Their rationale, she added, might run along the lines of, “I only have so much talent, and so many hours, and I need them to only work on things where I can measure and see the return on investment.” 她还提到,一些工程经理可能会对贡献开源而减损核心产品的开发的精力而感到担忧。她补充到,他们的理由有可能是这样的:“我只有有限的才华与时间,且我需要这些只做我认为可以度量且看到投资回报的事情。” But that attitude, she said, is shortsighted. Supporting employees who contribute to open source communities can build skills and develop talent, she said. 但她说,这是一种鼠目寸光的态度。支持开源社区并且作出贡献的员工,可以从中培养技能与增长才华。 Loris Degionni, chief technology officer and founder at Sysdig, a cloud security vendor, echoed this notion: “Finding employees who contribute to open source is a gold mine,” said. 云安全供应商 Sysdig 的首席技术官兼创始人 Loris Degionni 也赞同这一观点:“找出为开源做出贡献的员工无疑就找到一座金矿,”他说。 These employees are more capable of delivering features a company wants to use and merge them into community-supported standards, he said. And in a war for talent, companies that embrace open source are more attractive to developers. 他认为,这些参与开源的员工更具备公司想拥有的竞争力并将一些功能融入至社区所支持的标准中。且在人才争夺战中,拥抱开源的公司也更受到开发人员的青睐。 “Lastly, open source is driven by a community of technical experts you may not be able to hire,” he said. “When employees actively contribute and collaborate with these experts, they’ll be better informed of best practices and bring them back to your organization. “最后,开源项目是由你可能无法聘请的技术专家社区推动的”,他说,“当员工积极参与并于这些专家合作时,他们将能更好地深入这些最佳实践,并将这些收获带回到你的组织之中。” “You start to grow technical debt because when the original source changes and you’ve got a different version … It doesn’t take long for you to be the proud user and maintainer of a one-of-a-kind open source project variant.” —Suzanne Ambiel, director, open source marketing and strategy, VMware “当原始数据来源发生变化且你所使用的是不同的版本时,你的技术负债将越来越多...所以你很快就会变成一个开源项目里独一无二变体的”自豪“用户和维护人员。” — Suzanne Ambiel,VMware 开源营销和战略总监 “All of this should be rewarded — developers shouldn’t have to spend their free time honing their skills, as your company will quickly see benefits from their efforts.” “但是这一切终究不会白费--开发人员不应该把业余时间用在磨练他们的技能上,因为你的公司很快就会在他们的努力中看到好处。” An OSPO, Degionni suggested, can help achieve these goals, as well as help prioritize contributions and ensure collaboration. In addition, they can help provide governance that mirrors what companies would have for internally developed applications. Degionni认为,OSPO(开源计划办公室)可以帮助公司实现这些目标,以及帮助确定贡献的优先级并确保合作的进行。除此之外,他们也可以对公司内部开发应用程序方面的治理提供相关帮助。 “Members of the open source team are also in a position to be great internal evangelists for open source technologies, and act as bridges between the organization and the broader community,” he added. “开源团队的成员也可以成为开源技术的伟大内部布道师,并充当组织与更广泛社区之间的桥梁。”他补充道。 In the September survey from The New Stack, Linux Foundation Research and the TODO Group, nearly 53% of organizations with OSPOs said they saw more innovation as a result of having an OSPO, while almost 43% said they saw increased participation in external open source projects. 在 The New Stack、Linux Foundation Research 和 TODO Group 的 9 月调查中,近 53% 的拥有 OSPO的组织表示,由于拥有了OSPO,他们看到了更多创新,而近 43% 的组织表示,他们在外部开源项目的参与度上有所增加。 Part3More OSPO Benefits:A Business Edge Contributing to open source communities doesn’t just help the communities, but the companies that contribute to them, said Tom Hickman, chief innovation officer at ThreatX, a cybersecurity firm. 网络安全公司 ThreatX 的首席创新官 Tom Hickman 表示,为开源社区做出贡献,不仅有助于社区,还有助于为社区做出贡献的公司。 “Growing the community of developers around a project helps the code base, and attracts more developers,” he said. “It can become a virtuous circle.” “围绕一个项目而发展的开发人员社区,有助于代码库的形成,并吸引更多的开发人员参与”,他说,“这可以变成一个良性循环。” Also, companies that contribute to open source projects get twice the productive value from their use of open source than companies that don’t, according to research by Harvard Business School. 此外,根据哈佛商学院的研究,为开源项目作出贡献的公司从使用开源的项目中获得的生产价值,是不参与开源项目公司的两倍。 Many of the biggest companies in the world are contributing to open source, said Chris Aniszczyk, chief technology officer at Cloud Native Computing Foundation. He pointed to the Open Source Contributor Index as a reference for exactly just how much companies are doing. Cloud Native Computing Foundation 的首席技术官 Chris Aniszczyk 说,世界上许多巨头公司都为开源作出了贡献。他还提到,开源贡献者的指数是作为公司是否有所作为的参考。 The tech giants dominate the list: Google, Microsoft, Red Hat, Intel, IBM, Amazon, Facebook, VMware, GitHub and SAP are the top 10 contributors, in that order. But there are also a lot of end users on the top 100 list, said Aniszczyk, including Uber, the BBC, Orange, Netflix, and Square. 科技巨头占据了这份榜单的主导地位:谷歌、微软、红帽、英特尔、IBM、亚马逊、Facebook、VMware、GitHub 和 SAP 依次是排名前 10 的贡献者。但Aniszczyk 表示,但也有很多终端用户公司进入前 100 名,包括 Uber、BBC、Orange、Netflix 和 Square。 “We’ve always known working in upstream projects is not just the right thing to do —it’s the best approach to open source software development and the best way to deliver open source benefits to our customers,” he said. “It’s great to see that IT leaders recognize this as well.” “我们一直知道,在上游项目中工作不仅仅是关正确与否----它是开源软件开发的最佳方法,也是向客户提供开源福利的最佳方式“他说,“很高兴看到IT领导者们也认识到了这一点。” To contribute alongside these giants, companies need to have their own open source strategies, and having an open source program office can help. 为了和这些公司一起作出贡献,公司也需要有自己的开源策略,而拥有一个开源项目办公室则可以为其提供帮助。 “OSPOs provide a critical center of competency in a company when it comes to utilizing open source software,” he said. “在使用开源软件方面,OPSO为公司提供了一个至关重要的能力中心”他说。 It’s similar to the way that companies have security operations centers, he said. 这与公司拥有安全运营中心的方式类似,他说。 “Growing the community of developers around a project helps the code base, and attracts more developers. It can become a virtuous circle.” —Tom Hickman, chief innovation officer, ThreatX “围绕一个项目而发展的开发人员社区,有助于代码库的形成,并吸引更多的开发人员参与,这可以变成一个良性循环。” ——Tom Hickman,ThreatX 首席创新官 “If you don’t make the investment in a security team, you generally don’t expect your software to be secure or be able to respond to security incidents in a timely fashion,” he said. “如果你没有对安全团队进行相应投资,你通常是不会期望你的软件是安全的,也无法及时响应安全事件。”他说。 “The same logic applies to OSPOs and is why you see many leading companies out there such as Apple, Meta, Twitter, Goldman Sachs, Bloomberg, and Google all have OSPOs. They are ahead of the curve.” “同样的逻辑也适用于 OSPO,这就是为什么你会看到许多领先的公司,例如 Apple、Meta、Twitter、Goldman Sachs、Bloomberg 和 Google 都拥有 OSPO。他们走在了趋势的前面。” Support for open source activity within your organization can become a differentiator and marketing opportunity for software vendors. 而对组织内的开源活动的支持态度亦可成为软件供应商们的差异化原因与营销的机会。 According to a Red Hat survey released in February, 82% of IT leaders are more likely to select a vendor who contributes to the open source community. 根据Red Hat2月分发布的一项调查,82%的IT领导者更倾向于选择为开源社区作出贡献的软件供应商。 Respondents said that when vendors support open source communities they are more familiar with open source processes and are more effective if customers have technical challenges. 受访者表示,当供应商支持开源社区时,就表示着他们更熟悉开源的流程并且在客户遇到技术难题时会更加有效。 But it’s not just software vendors who benefit. 但收益的不仅仅是软件供应商们。 According to September’s survey by The New Stack, Linux Foundation Research, and the TODO Group, 57% of organizations with OSPOs use them to further strategic relationships and build partnerships. 根据 The New Stack、Linux Foundation Research 和 TODO Group 9 月份的调查,57% 拥有 OSPO 的组织将使用它们来进一步发展战略关系和建立合作伙伴关系。 Mark Hinkle started an open source program office back when he worked at Citrix a decade ago. He pointed out how having an OSPO in-house benefited the company. 十年前,Mark Hinkle 在 Citrix 工作时创办了一个开源计划办公室。他指出了在内部拥有一个 OSPO将如何使公司受益。 “For us the biggest job was to educate our employees who weren’t familiar with open source to get involved and be good community members,” he said. “We also provided guidance on how to make sure our IP didn’t enter projects without proper understanding and we made sure we didn’t incorporate open source that conflicted with our enterprise software licensing.” “对于我们来说,最大的工作是让不熟悉开源的员工学会并参与其中,成为优秀的社区成员”,他说,“我们还就如何确保我们的IP不会在没有正确理解的情况下进入项目的情况提供了指导,并确保我们没有与我们企业软件许可相冲突的开源项目合作。” The OSPO also helped Citrix identify strategic opportunities for the company to participate in open source projects and trade organizations like The Linux Foundation, he said. 他说,OSPO还帮助Citrix确定了公司参与开源项目和Linux基金会等贸易组织的战略机会。 Today, he’s the CEO and co-founder of TriggerMesh, a cloud native, open source integration platform. 如今,他是云原生开源集成平台 TriggerMesh 的首席执行官兼联合创始人。 There are some significant economic benefits to participating in the open source ecosystem, he said. 他说,参与开源系统对公司来说有着重大的经济效益。 “We participate in Knative to share the development of our underlying platform but we develop value-added services as part of our business,” he said. “By sharing the R and D for the platform, it gives us more resources to develop our own differentiated technology.” “我们参与Knative是为了分享我们基础底层平台的开发,但作为业务的一部分,我们也拥有相关的增值服务。”他说,“通过共享该平台的研发,这为我们提供了更多的资源来改进我们自己的差异化技术。” Part4How to Get Started in Open Source Sixty-three percent of companies in the September survey from The New Stack, Linux Foundation Research and the TODO Group said that having an OSPO was very or extremely critical to the success of their engineering or product teams, up from 54% in the previous annual study. 在 The New Stack、Linux Foundation Research 和 TODO Group 的 9 月份调查中,有 63% 的公司表示,拥有OSPO 对其工程或产品团队的成功至关重要,高于上一年度该项研究数据的 54%。 In particular, 77% said that their open source program had a positive impact on their software practices, such as improved code quality. 其中77% 的人表示他们的开源程序对他们的软件实践产生了积极影响,例如提高了代码质量。 But companies can’t always contribute to every single open source project that they use. 但公司也不可能总是为他们使用的每一个开源项目而花费精力。 “First, thin the herd a little bit,” advised VMware’s Ambiel. “首先,节流一下”,VMware 的 Ambiel 建议道。 Companies should look at the projects that make the most sense for their use cases. This is an area where an OSPO can help set priorities and ensure technical and strategic alignment. 公司应该关注投入使用中最有意义的项目。而这也是OSPO可以帮助确定优先事项并确保技术与战略一致性的领域。 Then, developers should go and check out the projects themselves. Projects typically offer online documentation, often with contributor guides, governance documents, and lists of open issues. 之后,开发人员应该自己去了解一下。项目通常提供相关在线文档,一般包含贡献着指南、治理文档和未解决问题列表。 “For the projects that rise to the top of your strategic list, introduce yourself — say hello,” she said. “Go to the Slack channel or the distribution list and ask where they need help. Maybe they don’t need help and everything is good. Or maybe they can use a new person to review code.” “对于那些上升到你的战略清单顶端的项目,你可以介绍一下自己----打个招呼”,她说。“然后转到Slack频道或者分发列表,询问他们需要帮助的地方。也许他们不需要帮助,一切完好;又或者他们也有可能使用新人来审查核验代码。” An open source program office can not only help make a business case for contributing to the open source community, Ambiel said, but can help companies do it in a way that’s safe, secure and sound. Ambiel 说,开源项目办公室不仅可以帮助制定为开源社区做出贡献的商业案例,还可以帮助公司以安全、可靠和健全的方式来做这件事。 “If I work for a company and want to contribute to open source, I don’t want to accidentally disclose, divulge or undermine any patents,” she said. “An OSPO helps you make smart choices.” “如果我为一家公司工作,并想为开源做出贡献,我不想意外披露、泄露或破坏任何专利,”她说。“而OSPO可以帮助您做出明智的选择。” An OSPO can also help provide leadership and the guiding philosophy about supporting open source, she said. “It can provide guidance, mentorship, coaching and best practices.” 她说,OSPO还可以在开源方面提供领导力和指导理念的支持。“它可以提供引领、指导、辅导和最佳实践的作用。” Commitment to support open source has to start at the top, said Anaïs Urlichs, developer advocate at Aqua Security. Aqua Security的开发人员倡导者Anaïs Urlichs则认为,支持开源的承诺必须从高层开始。 “Too often,” she said, “companies do not value investment into open source, so employees are not encouraged to contribute to it.” 她说,“公司在多数时候往往不重视对开源的投资,所以员工自然而然不被鼓励对此作出贡献。” In those cases, employees with a passion for open source end up contributing during their free time, which is not sustainable. 在这些情况下,员工对于开源的热情也会在空闲时间里对开源的建设而消散殆尽,这对于开源的发展来说是不可持续的。 “If companies rely on open source projects, it is important to make open source contributions part of an engineer’s work schedule,” she said. “Some companies define a time percentage that employees can contribute to open source as part of their normal workday.” “如果公司对开源项目依赖度高,那么将开源贡献纳入工程师的日程安排是很重要的,”她说。“一些公司定义了员工可以为开源建设的时间百分比,将其作为他们正常工作日的一部分。” The New Stack is a wholly owned subsidiary of Insight Partners, an investor in the following companies mentioned in this article: Sysdig, Aqua Security. The New Stack 是 Insight Partners 的全资子公司,Insight Partners 是本文提到的以下公司的投资者:Sysdig、Aqua Security。 相关阅读 | Related Reading 《开源合规指南(企业篇)》正式发布,为推动我国开源合规建设提供参考 “目标->用户->指标”——企业开源运营之道|瞰道@谭中意 开源之夏邀请函——仅限高校学子开启 开源社简介 开源社成立于 2014 年,是由志愿贡献于开源事业的个人成员,依 “贡献、共识、共治” 原则所组成,始终维持厂商中立、公益、非营利的特点,是最早以 “开源治理、国际接轨、社区发展、开源项目” 为使命的开源社区联合体。开源社积极与支持开源的社区、企业以及政府相关单位紧密合作,以 “立足中国、贡献全球” 为愿景,旨在共创健康可持续发展的开源生态,推动中国开源社区成为全球开源体系的积极参与及贡献者。 2017 年,开源社转型为完全由个人成员组成,参照 ASF 等国际顶级开源基金会的治理模式运作。近八年来,链接了数万名开源人,集聚了上千名社区成员及志愿者、海内外数百位讲师,合作了近百家赞助、媒体、社区伙伴。 本篇文章为转载内容。原文链接:https://blog.csdn.net/kaiyuanshe/article/details/124976824。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-05-03 09:19:23
273
转载
转载文章
...指向堆外内存(直接被操作系统管理的内存),JVM无法对其回收 当虚引用对象被回收时,JVM的垃圾回收无法自动回收堆外内存, 但是此时,虚引用对象被回收,会将其放在队列中 操作人员,看到队列中有对象被回收,就进行相应操作,回收堆内存 如何回收堆外内存 C和C++有函数可以用 java现在也提供了Unsafe类可以操作堆外内存,具体请参考上一篇博客,总之,JDK1.8只能通过反射来用,JDK1.9以上可以通过new Unsafe对象来用 Unsafe类的方法有: copyMemory():直接访问内存 allocateMemory():直接分配内存,这就必须手动回收内存了 freeMemory():回收内存 下面是一个虚引用例子,自己看吧,懂得自然懂,现在看不懂的,先收藏或者保存上,以后回来看 / 一个对象是否有虚引用的存在,完全不会对其生存时间构成影响, 也无法通过虚引用来获取一个对象的实例。 为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。 虚引用和弱引用对关联对象的回收都不会产生影响,如果只有虚引用活着弱引用关联着对象, 那么这个对象就会被回收。它们的不同之处在于弱引用的get方法,虚引用的get方法始终返回null, 弱引用可以使用ReferenceQueue,虚引用必须配合ReferenceQueue使用。 jdk中直接内存的回收就用到虚引用,由于jvm自动内存管理的范围是堆内存, 而直接内存是在堆内存之外(其实是内存映射文件,自行去理解虚拟内存空间的相关概念), 所以直接内存的分配和回收都是有Unsafe类去操作,java在申请一块直接内存之后, 会在堆内存分配一个对象保存这个堆外内存的引用, 这个对象被垃圾收集器管理,一旦这个对象被回收, 相应的用户线程会收到通知并对直接内存进行清理工作。 事实上,虚引用有一个很重要的用途就是用来做堆外内存的释放, DirectByteBuffer就是通过虚引用来实现堆外内存的释放的。/import java.lang.ref.PhantomReference;import java.lang.ref.Reference;import java.lang.ref.ReferenceQueue;import java.util.LinkedList;import java.util.List;public class T04_PhantomReference {private static final List<Object> LIST = new LinkedList<>();private static final ReferenceQueue<M> QUEUE = new ReferenceQueue<>();public static void main(String[] args) {PhantomReference<M> phantomReference = new PhantomReference<>(new M(), QUEUE);new Thread(() -> {while (true) {LIST.add(new byte[1024 1024]);try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();Thread.currentThread().interrupt();}System.out.println(phantomReference.get());} }).start();new Thread(() -> {while (true) {Reference<? extends M> poll = QUEUE.poll();if (poll != null) {System.out.println("--- 虚引用对象被jvm回收了 ---- " + poll);} }}).start();try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();} }} 2、容器 1、发展历史(一定要了解) map容器你需要了解的历史 JDK早期,java提供了Vector和Hashtable两个容器,这两个容器,很多操作都加了锁Synchronized,对于某些不需要用锁的情况下,就显得十分影响性能,所以现在基本没人用这两个容器,但是面试经常问这两个容器里面的数据结构等内容 后来,出现了HashMap,此容器完全不加锁,是用的最多的容器 但是完全不加锁未免不完善,所以java提供了如下方式,将HashMap变为加锁的 //通过Collections.synchronizedMap(HashMap)方法,将其变为加锁Map集合,其中泛型随意,UUID只是举例。static Map<UUID, UUID> m = Collections.synchronizedMap(new HashMap<UUID, UUID>()); 通过阅读源码发现,上面方法将HashMap变为加锁,也是使用Synchronized,只是锁的内容更细,但并不比HashTable效率高多少 所以衍生除了新的容器ConcurrentHashMap ConcurrentHashMap 此容器,插入效率不如上面的,因为它做了各种判断和CAS,但是差距不是特别大 读取效率很高,100个线程同时访问,每个线程读取一百万次实测 Hashtable 39s ,SynchronizedHashMap 38s ,ConcurrentHashMap 1.7s 前两个将近40秒,ConcurrentHashMap只需要不到2s,由此可见此容器读取效率极高 2、为什么推荐使用Queue来做高并发 为什么推荐Queue(队列) Queue接口提供了很多针对多线程非常友好的API(offer ,peek和poll,其中BlockingQueue还添加了put和take可以阻塞),可以说专门为多线程高并发而创造的接口,所以一般我们使用Queue而不用List 以下代码分别使用链表LinkList和ConcurrentQueue,对比一下速度 LinkList用了5s多,ConcurrentQueue几乎瞬间完成 Concurrent接口就是专为多线程设计,多线程设计要多考虑Queue(高并发用)的使用,少使用List / 有N张火车票,每张票都有一个编号 同时有10个窗口对外售票 请写一个模拟程序 分析下面的程序可能会产生哪些问题? 重复销售?超量销售? 使用Vector或者Collections.synchronizedXXX 分析一下,这样能解决问题吗? 就算操作A和B都是同步的,但A和B组成的复合操作也未必是同步的,仍然需要自己进行同步 就像这个程序,判断size和进行remove必须是一整个的原子操作 @author 马士兵/import java.util.LinkedList;import java.util.List;import java.util.concurrent.TimeUnit;public class TicketSeller3 {static List<String> tickets = new LinkedList<>();static {for(int i=0; i<1000; i++) tickets.add("票 编号:" + i);}public static void main(String[] args) {for(int i=0; i<10; i++) {new Thread(()->{while(true) {synchronized(tickets) {if(tickets.size() <= 0) break;try {TimeUnit.MILLISECONDS.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("销售了--" + tickets.remove(0));} }}).start();} }} 队列 import java.util.Queue;import java.util.concurrent.ConcurrentLinkedQueue;public class TicketSeller4 {static Queue<String> tickets = new ConcurrentLinkedQueue<>();static {for(int i=0; i<1000; i++) tickets.add("票 编号:" + i);}public static void main(String[] args) {for(int i=0; i<10; i++) {new Thread(()->{while(true) {String s = tickets.poll();if(s == null) break;else System.out.println("销售了--" + s);} }).start();} }} 3、多线程常用容器 1、ConcurrentHashMap(无序)和ConcurrentSkipListMap(有序,链表,使用跳表数据结构,让查询更快) 跳表:http://blog.csdn.net/sunxianghuang/article/details/52221913 import java.util.;import java.util.concurrent.ConcurrentHashMap;import java.util.concurrent.ConcurrentSkipListMap;import java.util.concurrent.CountDownLatch;public class T01_ConcurrentMap {public static void main(String[] args) {Map<String, String> map = new ConcurrentHashMap<>();//Map<String, String> map = new ConcurrentSkipListMap<>(); //高并发并且排序//Map<String, String> map = new Hashtable<>();//Map<String, String> map = new HashMap<>(); //Collections.synchronizedXXX//TreeMapRandom r = new Random();Thread[] ths = new Thread[100];CountDownLatch latch = new CountDownLatch(ths.length);long start = System.currentTimeMillis();for(int i=0; i<ths.length; i++) {ths[i] = new Thread(()->{for(int j=0; j<10000; j++) map.put("a" + r.nextInt(100000), "a" + r.nextInt(100000));latch.countDown();});}Arrays.asList(ths).forEach(t->t.start());try {latch.await();} catch (InterruptedException e) {e.printStackTrace();}long end = System.currentTimeMillis();System.out.println(end - start);System.out.println(map.size());} } 2、CopyOnWriteList(写时复制)和CopyOnWriteSet 适用于,高并发是,读的多,写的少的情况 当我们写的时候,将容器复制,让写线程去复制的线程写(写的时候加锁) 而读线程依旧去读旧的(读的时候不加锁) 当写完,将对象指向复制后的已经写完的容器,原来容器销毁 大大提高读的效率 / 写时复制容器 copy on write 多线程环境下,写时效率低,读时效率高 适合写少读多的环境 @author 马士兵/import java.util.ArrayList;import java.util.Arrays;import java.util.List;import java.util.Random;import java.util.Vector;import java.util.concurrent.CopyOnWriteArrayList;public class T02_CopyOnWriteList {public static void main(String[] args) {List<String> lists = //new ArrayList<>(); //这个会出并发问题!//new Vector();new CopyOnWriteArrayList<>();Random r = new Random();Thread[] ths = new Thread[100];for(int i=0; i<ths.length; i++) {Runnable task = new Runnable() {@Overridepublic void run() {for(int i=0; i<1000; i++) lists.add("a" + r.nextInt(10000));} };ths[i] = new Thread(task);}runAndComputeTime(ths);System.out.println(lists.size());}static void runAndComputeTime(Thread[] ths) {long s1 = System.currentTimeMillis();Arrays.asList(ths).forEach(t->t.start());Arrays.asList(ths).forEach(t->{try {t.join();} catch (InterruptedException e) {e.printStackTrace();} });long s2 = System.currentTimeMillis();System.out.println(s2 - s1);} } 3、synchronizedList和ConcurrentLinkedQueue package com.mashibing.juc.c_025;import java.util.ArrayList;import java.util.Collections;import java.util.List;import java.util.Queue;import java.util.concurrent.ConcurrentLinkedQueue;public class T04_ConcurrentQueue {public static void main(String[] args) {List<String> strsList = new ArrayList<>();List<String> strsSync = Collections.synchronizedList(strsList);//加锁ListQueue<String> strs = new ConcurrentLinkedQueue<>();//Concurrent链表队列,就是读快for(int i=0; i<10; i++) {strs.offer("a" + i); //add添加,但是不同点是,此方法会返回一个布尔值}System.out.println(strs);System.out.println(strs.size());System.out.println(strs.poll());//取出,取完后将元素去除System.out.println(strs.size());System.out.println(strs.peek());//取出,但是不会将元素从队列删除System.out.println(strs.size());//双端队列Deque} } 4、LinkedBlockingQueue 链表阻塞队列(无界链表,可以一直装东西,直到内存满(其实,也不是无限,其长度Integer.MaxValue就是上限,毕竟最大就这么大)) 主要体现在put和take方法,put添加的时候,如果队列满了,就阻塞当前线程,直到队列有空位,继续插入。take方法取的时候,如果没有值,就阻塞,等有值了,立马去取 import java.util.Random;import java.util.concurrent.BlockingQueue;import java.util.concurrent.LinkedBlockingQueue;import java.util.concurrent.TimeUnit;public class T05_LinkedBlockingQueue {static BlockingQueue<String> strs = new LinkedBlockingQueue<>();static Random r = new Random();public static void main(String[] args) {new Thread(() -> {for (int i = 0; i < 100; i++) {try {strs.put("a" + i); //如果满了,当前线程就会等待(实现阻塞),等多会有空位,将值插入TimeUnit.MILLISECONDS.sleep(r.nextInt(1000));} catch (InterruptedException e) {e.printStackTrace();} }}, "p1").start();for (int i = 0; i < 5; i++) {new Thread(() -> {for (;;) {try {System.out.println(Thread.currentThread().getName() + " take -" + strs.take()); //取内容,如果空了,当前线程就会等待(实现阻塞)} catch (InterruptedException e) {e.printStackTrace();} }}, "c" + i).start();} }} 5、ArrayBlockingQueue 有界阻塞队列(因为Array需要指定长度) import java.util.Random;import java.util.concurrent.ArrayBlockingQueue;import java.util.concurrent.BlockingQueue;import java.util.concurrent.TimeUnit;public class T06_ArrayBlockingQueue {static BlockingQueue<String> strs = new ArrayBlockingQueue<>(10);static Random r = new Random();public static void main(String[] args) throws InterruptedException {for (int i = 0; i < 10; i++) {strs.put("a" + i);}//strs.put("aaa"); //满了就会等待,程序阻塞//strs.add("aaa");//strs.offer("aaa");strs.offer("aaa", 1, TimeUnit.SECONDS);System.out.println(strs);} } 6、特殊的阻塞队列1:DelayQueue 延时队列(按时间进行调度,就是隔多长时间运行,谁隔的少,谁先) 以下例子中,我们添加线程到队列顺序为t12345,正常情况下,会按照顺序运行,但是这里有了延时时间,也就是时间越短,越先执行 步骤很简单,拿到延时队列 指定构造方法 继承 implements Delayed 重写 compareTo和getDelay import java.util.Calendar;import java.util.Random;import java.util.concurrent.BlockingQueue;import java.util.concurrent.DelayQueue;import java.util.concurrent.Delayed;import java.util.concurrent.TimeUnit;public class T07_DelayQueue {static BlockingQueue<MyTask> tasks = new DelayQueue<>();static Random r = new Random();static class MyTask implements Delayed {String name;long runningTime;MyTask(String name, long rt) {this.name = name;this.runningTime = rt;}@Overridepublic int compareTo(Delayed o) {if(this.getDelay(TimeUnit.MILLISECONDS) < o.getDelay(TimeUnit.MILLISECONDS))return -1;else if(this.getDelay(TimeUnit.MILLISECONDS) > o.getDelay(TimeUnit.MILLISECONDS)) return 1;else return 0;}@Overridepublic long getDelay(TimeUnit unit) {return unit.convert(runningTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS);}@Overridepublic String toString() {return name + " " + runningTime;} }public static void main(String[] args) throws InterruptedException {long now = System.currentTimeMillis();MyTask t1 = new MyTask("t1", now + 1000);MyTask t2 = new MyTask("t2", now + 2000);MyTask t3 = new MyTask("t3", now + 1500);MyTask t4 = new MyTask("t4", now + 2500);MyTask t5 = new MyTask("t5", now + 500);tasks.put(t1);tasks.put(t2);tasks.put(t3);tasks.put(t4);tasks.put(t5);System.out.println(tasks);for(int i=0; i<5; i++) {System.out.println(tasks.take());//获取的是toString方法返回值} }} 7、特殊的阻塞队列2:PriorityQueque 优先队列(二叉树算法,就是排序) import java.util.PriorityQueue;public class T07_01_PriorityQueque {public static void main(String[] args) {PriorityQueue<String> q = new PriorityQueue<>();q.add("c");q.add("e");q.add("a");q.add("d");q.add("z");for (int i = 0; i < 5; i++) {System.out.println(q.poll());} }} 8、特殊的阻塞队列3:SynchronusQueue 同步队列(线程池用处非常大) 此队列容量为0,当插入元素时,必须同时有个线程往外取 就是说,当你往这个队列里面插入一个元素,它就拿着这个元素站着(阻塞),直到有个取元素的线程来,它就把元素交给它 就是用来同步数据的,也就是线程间交互数据用的一个特殊队列 package com.mashibing.juc.c_025;import java.util.concurrent.BlockingQueue;import java.util.concurrent.SynchronousQueue;public class T08_SynchronusQueue { //容量为0public static void main(String[] args) throws InterruptedException {BlockingQueue<String> strs = new SynchronousQueue<>();new Thread(()->{//这个线程就是消费者,来取值try {System.out.println(strs.take());//和同步队列要值} catch (InterruptedException e) {e.printStackTrace();} }).start();strs.put("aaa"); //阻塞等待消费者消费,就拿着aaa站着,等线程来取//strs.put("bbb");//strs.add("aaa");System.out.println(strs.size());} } 9、特殊的阻塞队列4:TransferQueue 传递队列 此队列加入了一个方法transfer()用来向队列添加元素 但是和put()方法不同的是,put添加完元素就走了 而这个方法,添加完自己就阻塞了,直到有人将这个元素取走,它才继续工作(省去我们手动阻塞) import java.util.concurrent.LinkedTransferQueue;public class T09_TransferQueue {public static void main(String[] args) throws InterruptedException {LinkedTransferQueue<String> strs = new LinkedTransferQueue<>();new Thread(() -> {try {System.out.println(strs.take());} catch (InterruptedException e) {e.printStackTrace();} }).start();strs.transfer("aaa");//放东西到队列,同时阻塞等待消费者线程,取走元素//strs.put("aaa");//如果用put就和普通队列一样,放完东西就走了/new Thread(() -> {try {System.out.println(strs.take());} catch (InterruptedException e) {e.printStackTrace();} }).start();/} } 3、线程池 线程池 由于单独创建线程,十分影响效率,而且无法对线程集中管理,一旦疏落,可能线程无限执行,浪费资源 线程池就是一个存储线程的游泳池,而每个线程就是池子里面的赛道 池子里的线程不执行任何任务,只是提供一个资源 而谁提交了任务,比如我想来游泳,那么池子就给你一个赛道,让你游泳 比如它想练憋气,那么给它一个赛道练憋气 当他们用完,走了,那么后面其它人再过来继续用 这就是线程池,始终只有这几个线程,不做实现,而是借用这几个线程的用户,自己掌控用这些线程资源做什么(提交任务给线程,线程空闲就帮他们完成任务) 线程池的两种类型(两类,不是两个) ThreadPoolExecutor(简称TPE) ForkJoinPool(分解汇总任务(将任务细化,最后汇总结果),少量线程执行多个任务(子任务,TPE做不到先执行子任务),CPU密集型) Executors(注意这后面有s) 它可以说是线程池工厂类,我们一般通过它创建线程池,并且它为我们封装了线程 1、常用类 Executor ExecutorService 扩展了execute方法,具有一个返回值 规定了异步执行机制,提供了一些执行器方法,比如shutdown()关闭等 但是它不知道执行器中的线程何时执行完 Callable 对Runnable进行了扩展,实现Callable的调用,可以有返回值,表示线程的状态 但是无法返回线程执行结果 Future 获得未来线程执行结果 由此,我们可以得知线程池基本的一个使用步骤 其中service.submit():为异步提交,也就是说,主线程该干嘛干嘛,我是异步执行的,和同步不一样(当前线程执行完,主线程才能继续执行,叫同步) futuer.get():获取结果集结果,此时因为异步,主线程执行到这里,结果集可能还没封装好,所以此时如果没有值,就阻塞,直到结果集出来 public static void main(String[] args) throws ExecutionException, InterruptedException {Callable<String> c = new Callable() {@Overridepublic String call() throws Exception {return "Hello Callable";} };ExecutorService service = Executors.newCachedThreadPool();Future<String> future = service.submit(c); //异步System.out.println(future.get());//阻塞service.shutdown();} 2、FutureTask 可充当任务的结果集 上面我们介绍Future是用来得到任务的执行结果的 而FutureTask,可以当做一个任务用,并且返回任务的结果,也就是可以跑线程,然后还可以得到线程结果 public static void main(String[] args) throws InterruptedException, ExecutionException {FutureTask<Integer> task = new FutureTask<>(()->{TimeUnit.MILLISECONDS.sleep(500);return 1000;}); //new Callable () { Integer call();}new Thread(task).start();System.out.println(task.get()); //阻塞} 3、CompletableFuture 非常灵活的任务结果集 一个非常灵活的结果集 他可以将很多执行不同任务的线程的结果进行汇总 比如一个网站,它可以启动多个线程去各大电商网站,比如淘宝,京东,收集某些或某一个商品的价格 最后,将获取的数据进行整合封装 最终,客户就可以通过此网站,获取某类商品在各网站的价格信息 / 假设你能够提供一个服务 这个服务查询各大电商网站同一类产品的价格并汇总展示 @author 马士兵 http://mashibing.com/import java.io.IOException;import java.util.Random;import java.util.concurrent.CompletableFuture;import java.util.concurrent.ExecutionException;import java.util.concurrent.TimeUnit;public class T06_01_CompletableFuture {public static void main(String[] args) throws ExecutionException, InterruptedException {long start, end;/start = System.currentTimeMillis();priceOfTM();priceOfTB();priceOfJD();end = System.currentTimeMillis();System.out.println("use serial method call! " + (end - start));/start = System.currentTimeMillis();CompletableFuture<Double> futureTM = CompletableFuture.supplyAsync(()->priceOfTM());CompletableFuture<Double> futureTB = CompletableFuture.supplyAsync(()->priceOfTB());CompletableFuture<Double> futureJD = CompletableFuture.supplyAsync(()->priceOfJD());CompletableFuture.allOf(futureTM, futureTB, futureJD).join();//当所有结果集都获取到,才汇总阻塞CompletableFuture.supplyAsync(()->priceOfTM()).thenApply(String::valueOf).thenApply(str-> "price " + str).thenAccept(System.out::println);end = System.currentTimeMillis();System.out.println("use completable future! " + (end - start));try {System.in.read();} catch (IOException e) {e.printStackTrace();} }private static double priceOfTM() {delay();return 1.00;}private static double priceOfTB() {delay();return 2.00;}private static double priceOfJD() {delay();return 3.00;}/private static double priceOfAmazon() {delay();throw new RuntimeException("product not exist!");}/private static void delay() {int time = new Random().nextInt(500);try {TimeUnit.MILLISECONDS.sleep(time);} catch (InterruptedException e) {e.printStackTrace();}System.out.printf("After %s sleep!\n", time);} } 4、TPE型线程池1:ThreadPoolExecutor 原理及其参数 线程池由两个集合组成,一个集合存储线程,一个集合存储任务 存储线程:可以规定大小,最多可以有多少个,以及指定核心线程数量(不会被回收) 任务队列:存储任务 细节:初始线程池没有线程,当有一个任务来,线程池起一个线程,又有一个任务来,再起一个线程,直到达到核心线程数量 核心线程数量达到时,新来的任务将存储到任务队列中等待核心线程处理完成,直到任务队列也满了 当任务队列满了,此时再次启动一个线程(非核心线程,一旦空闲,达到指定时间将会消失),直到达到线程最大数量 当线程容器和任务容器都满了,又来了线程,将会执行拒绝策略 上面的细节涉及的所有步骤内容,均由创建线程池的参数执行 下面是ThreadPoolExecutor构造方法参数的源码注释 / 用给定的初始值,创建一个新的线程池 @param corePoolSize 核心线程数量 @param maximumPoolSize 最大线程数量 @param keepAliveTime 当线程数大于核心线程数量时,空闲的线程可生存的时间 @param unit 时间单位 @param workQueue 任务队列,只能包含由execute提交的Runnable任务 @param threadFactory 工厂,用于创建线程给线程池调度的工厂,可以自定义 @param handler 拒绝策略(可以自定义,JDK默认提供4种),当线程边界和队列容量已经满了,新来线程被阻塞时使用的处理程序/public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler) JDK提供的4种拒绝策略,不常用,一般都是自己定义拒绝策略 Abort:抛异常 Discard:扔掉,不抛异常 DiscardOldest:扔掉排队时间最久的(将队列中排队时间最久的扔掉,然后让新来的进来) CallerRuns:调用者处理任务(谁通过execute方法提交任务,谁处理) ThreadPoolExecutor继承关系 继承关系:ThreadPoolExecutor->AbstractExectorService类->ExectorService接口->Exector接口 Executors(注意这后面有s) 它可以说是线程池工厂类,我们一般通过它创建线程池,并且它为我们封装了线程 看看下面创建线程池,哪里用到了它 使用实例 import java.io.IOException;import java.util.concurrent.;public class T05_00_HelloThreadPool {static class Task implements Runnable {private int i;public Task(int i) {this.i = i;}@Overridepublic void run() {System.out.println(Thread.currentThread().getName() + " Task " + i);try {System.in.read();} catch (IOException e) {e.printStackTrace();} }@Overridepublic String toString() {return "Task{" +"i=" + i +'}';} }public static void main(String[] args) {ThreadPoolExecutor tpe = new ThreadPoolExecutor(2, 4,60, TimeUnit.SECONDS,new ArrayBlockingQueue<Runnable>(4),Executors.defaultThreadFactory(),new ThreadPoolExecutor.CallerRunsPolicy());//创建线程池,核心2个,最大4个,空闲线程存活时间60s,任务队列容量4,使用默认线程工程,创建线程。拒绝策略是JDK提供的for (int i = 0; i < 8; i++) {tpe.execute(new Task(i));//供提交8次任务}System.out.println(tpe.getQueue());//查看任务队列tpe.execute(new Task(100));//提交新的任务System.out.println(tpe.getQueue());tpe.shutdown();//关闭线程池} } 5、TPE型线程池2:SingleThreadPool 单例线程池(只有一个线程) 为什么有单例线程池 有任务队列,有线程池管理机制 Executors(注意这后面有s) 它可以说是线程池工厂类,我们一般通过它创建线程池,并且它为我们封装了线程 看看下面哪里用到了它 /创建单例线程池,扔5个任务进去,查看输出结果,看看有几个线程执行任务/import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;public class T07_SingleThreadPool {public static void main(String[] args) {ExecutorService service = Executors.newSingleThreadExecutor();for(int i=0; i<5; i++) {final int j = i;service.execute(()->{System.out.println(j + " " + Thread.currentThread().getName());});} }} 6、TPE型线程池3:CachedPool 缓存,存储线程池 此线程池没有核心线程,来一个任务启动一个线程(最多Integer.MaxValue,不会放在任务队列,因为任务队列容量为0),每个线程空闲后,只能活60s 实例 import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;public class T07_SingleThreadPool {public static void main(String[] args) {ExecutorService service = Executors.newSingleThreadExecutor();//通过Executors获取池子for(int i=0; i<5; i++) {final int j = i;service.execute(()->{//提交任务System.out.println(j + " " + Thread.currentThread().getName());});}service.shutdown();} } 7、TPE型线程池4:FixedThreadPool 固定线程池 此线次池,用于创建一个固定线程数量的线程池,不会回收 实例 import java.util.ArrayList;import java.util.List;import java.util.concurrent.Callable;import java.util.concurrent.ExecutionException;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.Future;public class T09_FixedThreadPool {public static void main(String[] args) throws InterruptedException, ExecutionException {//并发执行long start = System.currentTimeMillis();getPrime(1, 200000); long end = System.currentTimeMillis();System.out.println(end - start);//输出并发执行耗费时间final int cpuCoreNum = 4;//并行执行ExecutorService service = Executors.newFixedThreadPool(cpuCoreNum);MyTask t1 = new MyTask(1, 80000); //1-5 5-10 10-15 15-20MyTask t2 = new MyTask(80001, 130000);MyTask t3 = new MyTask(130001, 170000);MyTask t4 = new MyTask(170001, 200000);Future<List<Integer>> f1 = service.submit(t1);Future<List<Integer>> f2 = service.submit(t2);Future<List<Integer>> f3 = service.submit(t3);Future<List<Integer>> f4 = service.submit(t4);start = System.currentTimeMillis();f1.get();f2.get();f3.get();f4.get();end = System.currentTimeMillis();System.out.println(end - start);//输出并行耗费时间}static class MyTask implements Callable<List<Integer>> {int startPos, endPos;MyTask(int s, int e) {this.startPos = s;this.endPos = e;}@Overridepublic List<Integer> call() throws Exception {List<Integer> r = getPrime(startPos, endPos);return r;} }static boolean isPrime(int num) {for(int i=2; i<=num/2; i++) {if(num % i == 0) return false;}return true;}static List<Integer> getPrime(int start, int end) {List<Integer> results = new ArrayList<>();for(int i=start; i<=end; i++) {if(isPrime(i)) results.add(i);}return results;} } 8、TPE型线程池5:ScheduledPool 预定,延时线程池 根据延时时间(隔多长时间后运行),排序,哪个线程先执行,用户只需要指定核心线程数量 此线程池返回的池对象,和提交任务方法都不一样,比较涉及到时间 import java.util.Random;import java.util.concurrent.Executors;import java.util.concurrent.ScheduledExecutorService;import java.util.concurrent.TimeUnit;public class T10_ScheduledPool {public static void main(String[] args) {ScheduledExecutorService service = Executors.newScheduledThreadPool(4);service.scheduleAtFixedRate(()->{//提交延时任务try {TimeUnit.MILLISECONDS.sleep(new Random().nextInt(1000));} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName());}, 0, 500, TimeUnit.MILLISECONDS);//指定延时时间和单位,第一个任务延时0毫秒,之后的任务,延时500毫秒} } 9、手写拒绝策略小例子 import java.util.concurrent.;public class T14_MyRejectedHandler {public static void main(String[] args) {ExecutorService service = new ThreadPoolExecutor(4, 4,0, TimeUnit.SECONDS, new ArrayBlockingQueue<>(6),Executors.defaultThreadFactory(),new MyHandler());//将手写拒绝策略传入}static class MyHandler implements RejectedExecutionHandler {//1、继承RejectedExecutionHandler@Overridepublic void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {//2、重写方法//log("r rejected")//伪代码,表示通过log4j.log()报一下日志,拒绝的时间,线程名//save r kafka mysql redis//可以尝试保存队列//try 3 times //可以尝试几次,比如3次,重新去抢队列,3次还不行就丢弃if(executor.getQueue().size() < 10000) {//尝试条件,如果size>10000了,就执行拒绝策略//try put again();//如果小于10000,尝试将其放到队列中} }} } 10、ForkJoinPool线程池1:ForkJoinPool 前面我们讲过线程分为两大类,TPE和FJP ForkJoinPool(分解汇总任务(将任务细化,最后汇总结果),少量线程执行多个任务(子任务,TPE做不到先执行子任务),CPU密集型) 适合将大任务切分成多个小任务运行 两个方法,fork():分子任务,将子任务分配到线程池中 join():当前任务的计算结果,如果有子任务,等子任务结果返回后再汇总 下面实例实现,一百万个随机数求和,由两种方法实现,一种ForkJoinPool分任务并行,一种使用单线程做 import java.io.IOException;import java.util.Arrays;import java.util.Random;import java.util.concurrent.ForkJoinPool;import java.util.concurrent.RecursiveAction;import java.util.concurrent.RecursiveTask;public class T12_ForkJoinPool {//1000000个随机数求和static int[] nums = new int[1000000];//一堆数static final int MAX_NUM = 50000;//分任务时,每个任务的操作量不能多于50000个,否则就继续细分static Random r = new Random();//使用随机数将数组初始化static {for(int i=0; i<nums.length; i++) {nums[i] = r.nextInt(100);}System.out.println("---" + Arrays.stream(nums).sum()); //stream api 单线程就这么做,一个一个加}//分任务,需要继承,可以继承RecursiveAction(不需要返回值,一般用在不需要返回值的场景)或//RecursiveTask(需要返回值,我们用这个,因为我们需要最后获取求和结果)两个更好实现的类,//他俩继承与ForkJoinTaskstatic class AddTaskRet extends RecursiveTask<Long> {private static final long serialVersionUID = 1L;int start, end;AddTaskRet(int s, int e) {start = s;end = e;}@Overrideprotected Long compute() {if(end-start <= MAX_NUM) {//如果任务操作数小于规定的最大操作数,就进行运算,long sum = 0L;for(int i=start; i<end; i++) sum += nums[i];return sum;//返回结果} //如果分配的操作数大于规定,就继续细分(简单的重中点分,两半)int middle = start + (end-start)/2;//获取中间值AddTaskRet subTask1 = new AddTaskRet(start, middle);//传入起始值和中间值,表示一个子任务AddTaskRet subTask2 = new AddTaskRet(middle, end);//中间值和结尾值,表示一个子任务subTask1.fork();//分任务subTask2.fork();//分任务return subTask1.join() + subTask2.join();//最后返回结果汇总} }public static void main(String[] args) throws IOException {/ForkJoinPool fjp = new ForkJoinPool();AddTask task = new AddTask(0, nums.length);fjp.execute(task);/ForkJoinPool fjp = new ForkJoinPool();//创建线程池AddTaskRet task = new AddTaskRet(0, nums.length);//创建任务fjp.execute(task);//传入任务long result = task.join();//返回汇总结果System.out.println(result);//System.in.read();} } 11、ForkJoinPool线程池2:WorkStealingPool 任务偷取线程池 原来的线程池,都是有一个任务队列,而这个不同,它给每个线程都分配了一个任务队列 当某一个线程的任务队列没有任务,并且自己空闲,它就去其它线程的任务队列中偷任务,所以叫任务偷取线程池 细节:当线程自己从自己的任务队列拿任务时,不需要加锁,但是偷任务时,因为有两个线程,可能发生同步问题,需要加锁 此线程继承FJP 实例 import java.io.IOException;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.TimeUnit;public class T11_WorkStealingPool {public static void main(String[] args) throws IOException {ExecutorService service = Executors.newWorkStealingPool();System.out.println(Runtime.getRuntime().availableProcessors());service.execute(new R(1000));service.execute(new R(2000));service.execute(new R(2000));service.execute(new R(2000)); //daemonservice.execute(new R(2000));//由于产生的是精灵线程(守护线程、后台线程),主线程不阻塞的话,看不到输出System.in.read(); }static class R implements Runnable {int time;R(int t) {this.time = t;}@Overridepublic void run() {try {TimeUnit.MILLISECONDS.sleep(time);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(time + " " + Thread.currentThread().getName());} }} 12、流式API:ParallelStreamAPI 不懂的请参考:https://blog.csdn.net/grd_java/article/details/110265219 实例 import java.util.ArrayList;import java.util.List;import java.util.Random;public class T13_ParallelStreamAPI {public static void main(String[] args) {List<Integer> nums = new ArrayList<>();Random r = new Random();for(int i=0; i<10000; i++) nums.add(1000000 + r.nextInt(1000000));//System.out.println(nums);long start = System.currentTimeMillis();nums.forEach(v->isPrime(v));long end = System.currentTimeMillis();System.out.println(end - start);//使用parallel stream apistart = System.currentTimeMillis();nums.parallelStream().forEach(T13_ParallelStreamAPI::isPrime);//并行流,将任务切分成子任务执行end = System.currentTimeMillis();System.out.println(end - start);}static boolean isPrime(int num) {for(int i=2; i<=num/2; i++) {if(num % i == 0) return false;}return true;} } 13、总结 总结 Callable相当于一Runnable但是它有返回值 Future:存储执行完产生的结果 FutureTask 相当于Future+Runnable,既可以执行任务,又能获取任务执行的Future结果 CompletableFuture 可以多任务异步,并对多任务控制,整合任务结果,细化完美,比如可以一个任务完成就可以整合结果,也可以所有任务完成才整合结果 4、ThreadPoolExecutor源码解析 依然只讲重点,实际还需要大家按照上篇博客中看源码的方式来看 1、常用变量的解释 // 1. ctl,可以看做一个int类型的数字,高3位表示线程池状态,低29位表示worker数量private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));// 2. COUNT_BITS,Integer.SIZE为32,所以COUNT_BITS为29private static final int COUNT_BITS = Integer.SIZE - 3;// 3. CAPACITY,线程池允许的最大线程数。1左移29位,然后减1,即为 2^29 - 1private static final int CAPACITY = (1 << COUNT_BITS) - 1;// runState is stored in the high-order bits// 4. 线程池有5种状态,按大小排序如下:RUNNING < SHUTDOWN < STOP < TIDYING < TERMINATEDprivate static final int RUNNING = -1 << COUNT_BITS;private static final int SHUTDOWN = 0 << COUNT_BITS;private static final int STOP = 1 << COUNT_BITS;private static final int TIDYING = 2 << COUNT_BITS;private static final int TERMINATED = 3 << COUNT_BITS;// Packing and unpacking ctl// 5. runStateOf(),获取线程池状态,通过按位与操作,低29位将全部变成0private static int runStateOf(int c) { return c & ~CAPACITY; }// 6. workerCountOf(),获取线程池worker数量,通过按位与操作,高3位将全部变成0private static int workerCountOf(int c) { return c & CAPACITY; }// 7. ctlOf(),根据线程池状态和线程池worker数量,生成ctl值private static int ctlOf(int rs, int wc) { return rs | wc; }/ Bit field accessors that don't require unpacking ctl. These depend on the bit layout and on workerCount being never negative./// 8. runStateLessThan(),线程池状态小于xxprivate static boolean runStateLessThan(int c, int s) {return c < s;}// 9. runStateAtLeast(),线程池状态大于等于xxprivate static boolean runStateAtLeast(int c, int s) {return c >= s;} 2、构造方法 public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler) {// 基本类型参数校验if (corePoolSize < 0 ||maximumPoolSize <= 0 ||maximumPoolSize < corePoolSize ||keepAliveTime < 0)throw new IllegalArgumentException();// 空指针校验if (workQueue == null || threadFactory == null || handler == null)throw new NullPointerException();this.corePoolSize = corePoolSize;this.maximumPoolSize = maximumPoolSize;this.workQueue = workQueue;// 根据传入参数unit和keepAliveTime,将存活时间转换为纳秒存到变量keepAliveTime 中this.keepAliveTime = unit.toNanos(keepAliveTime);this.threadFactory = threadFactory;this.handler = handler;} 3、提交执行task的过程 public void execute(Runnable command) {if (command == null)throw new NullPointerException();/ Proceed in 3 steps: 1. If fewer than corePoolSize threads are running, try to start a new thread with the given command as its first task. The call to addWorker atomically checks runState and workerCount, and so prevents false alarms that would add threads when it shouldn't, by returning false. 2. If a task can be successfully queued, then we still need to double-check whether we should have added a thread (because existing ones died since last checking) or that the pool shut down since entry into this method. So we recheck state and if necessary roll back the enqueuing if stopped, or start a new thread if there are none. 3. If we cannot queue task, then we try to add a new thread. If it fails, we know we are shut down or saturated and so reject the task./int c = ctl.get();// worker数量比核心线程数小,直接创建worker执行任务if (workerCountOf(c) < corePoolSize) {if (addWorker(command, true))return;c = ctl.get();}// worker数量超过核心线程数,任务直接进入队列if (isRunning(c) && workQueue.offer(command)) {int recheck = ctl.get();// 线程池状态不是RUNNING状态,说明执行过shutdown命令,需要对新加入的任务执行reject()操作。// 这儿为什么需要recheck,是因为任务入队列前后,线程池的状态可能会发生变化。if (! isRunning(recheck) && remove(command))reject(command);// 这儿为什么需要判断0值,主要是在线程池构造方法中,核心线程数允许为0else if (workerCountOf(recheck) == 0)addWorker(null, false);}// 如果线程池不是运行状态,或者任务进入队列失败,则尝试创建worker执行任务。// 这儿有3点需要注意:// 1. 线程池不是运行状态时,addWorker内部会判断线程池状态// 2. addWorker第2个参数表示是否创建核心线程// 3. addWorker返回false,则说明任务执行失败,需要执行reject操作else if (!addWorker(command, false))reject(command);} 4、addworker源码解析 private boolean addWorker(Runnable firstTask, boolean core) {retry:// 外层自旋for (;;) {int c = ctl.get();int rs = runStateOf(c);// 这个条件写得比较难懂,我对其进行了调整,和下面的条件等价// (rs > SHUTDOWN) || // (rs == SHUTDOWN && firstTask != null) || // (rs == SHUTDOWN && workQueue.isEmpty())// 1. 线程池状态大于SHUTDOWN时,直接返回false// 2. 线程池状态等于SHUTDOWN,且firstTask不为null,直接返回false// 3. 线程池状态等于SHUTDOWN,且队列为空,直接返回false// Check if queue empty only if necessary.if (rs >= SHUTDOWN &&! (rs == SHUTDOWN &&firstTask == null &&! workQueue.isEmpty()))return false;// 内层自旋for (;;) {int wc = workerCountOf(c);// worker数量超过容量,直接返回falseif (wc >= CAPACITY ||wc >= (core ? corePoolSize : maximumPoolSize))return false;// 使用CAS的方式增加worker数量。// 若增加成功,则直接跳出外层循环进入到第二部分if (compareAndIncrementWorkerCount(c))break retry;c = ctl.get(); // Re-read ctl// 线程池状态发生变化,对外层循环进行自旋if (runStateOf(c) != rs)continue retry;// 其他情况,直接内层循环进行自旋即可// else CAS failed due to workerCount change; retry inner loop} }boolean workerStarted = false;boolean workerAdded = false;Worker w = null;try {w = new Worker(firstTask);final Thread t = w.thread;if (t != null) {final ReentrantLock mainLock = this.mainLock;// worker的添加必须是串行的,因此需要加锁mainLock.lock();try {// Recheck while holding lock.// Back out on ThreadFactory failure or if// shut down before lock acquired.// 这儿需要重新检查线程池状态int rs = runStateOf(ctl.get());if (rs < SHUTDOWN ||(rs == SHUTDOWN && firstTask == null)) {// worker已经调用过了start()方法,则不再创建workerif (t.isAlive()) // precheck that t is startablethrow new IllegalThreadStateException();// worker创建并添加到workers成功workers.add(w);// 更新largestPoolSize变量int s = workers.size();if (s > largestPoolSize)largestPoolSize = s;workerAdded = true;} } finally {mainLock.unlock();}// 启动worker线程if (workerAdded) {t.start();workerStarted = true;} }} finally {// worker线程启动失败,说明线程池状态发生了变化(关闭操作被执行),需要进行shutdown相关操作if (! workerStarted)addWorkerFailed(w);}return workerStarted;} 5、线程池worker任务单元 private final class Workerextends AbstractQueuedSynchronizerimplements Runnable{/ This class will never be serialized, but we provide a serialVersionUID to suppress a javac warning./private static final long serialVersionUID = 6138294804551838833L;/ Thread this worker is running in. Null if factory fails. /final Thread thread;/ Initial task to run. Possibly null. /Runnable firstTask;/ Per-thread task counter /volatile long completedTasks;/ Creates with given first task and thread from ThreadFactory. @param firstTask the first task (null if none)/Worker(Runnable firstTask) {setState(-1); // inhibit interrupts until runWorkerthis.firstTask = firstTask;// 这儿是Worker的关键所在,使用了线程工厂创建了一个线程。传入的参数为当前workerthis.thread = getThreadFactory().newThread(this);}/ Delegates main run loop to outer runWorker /public void run() {runWorker(this);}// 省略代码...} 6、核心线程执行逻辑-runworker final void runWorker(Worker w) {Thread wt = Thread.currentThread();Runnable task = w.firstTask;w.firstTask = null;// 调用unlock()是为了让外部可以中断w.unlock(); // allow interrupts// 这个变量用于判断是否进入过自旋(while循环)boolean completedAbruptly = true;try {// 这儿是自旋// 1. 如果firstTask不为null,则执行firstTask;// 2. 如果firstTask为null,则调用getTask()从队列获取任务。// 3. 阻塞队列的特性就是:当队列为空时,当前线程会被阻塞等待while (task != null || (task = getTask()) != null) {// 这儿对worker进行加锁,是为了达到下面的目的// 1. 降低锁范围,提升性能// 2. 保证每个worker执行的任务是串行的w.lock();// If pool is stopping, ensure thread is interrupted;// if not, ensure thread is not interrupted. This// requires a recheck in second case to deal with// shutdownNow race while clearing interrupt// 如果线程池正在停止,则对当前线程进行中断操作if ((runStateAtLeast(ctl.get(), STOP) ||(Thread.interrupted() &&runStateAtLeast(ctl.get(), STOP))) &&!wt.isInterrupted())wt.interrupt();// 执行任务,且在执行前后通过beforeExecute()和afterExecute()来扩展其功能。// 这两个方法在当前类里面为空实现。try {beforeExecute(wt, task);Throwable thrown = null;try {task.run();} catch (RuntimeException x) {thrown = x; throw x;} catch (Error x) {thrown = x; throw x;} catch (Throwable x) {thrown = x; throw new Error(x);} finally {afterExecute(task, thrown);} } finally {// 帮助gctask = null;// 已完成任务数加一 w.completedTasks++;w.unlock();} }completedAbruptly = false;} finally {// 自旋操作被退出,说明线程池正在结束processWorkerExit(w, completedAbruptly);} } 本篇文章为转载内容。原文链接:https://blog.csdn.net/grd_java/article/details/113116244。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-07-21 16:19:45
328
转载
JQuery插件下载
...智能地根据已有的邮件数据库提供相关的自动提示建议。用户无需完全手动输入整个电子邮件地址,只需通过浏览下拉列表选择或直接使用tab键,即可快速、准确地完成邮箱地址的填充,极大地提高了用户的操作效率和页面的友好性。总结来说,这款jQuery电子邮件地址填写自动完成插件以其便捷的自动补全功能,有效提升了网站表单的用户体验,降低了邮箱输入出错的可能性,是开发者优化网页表单组件的理想工具之一。 点我下载 文件大小:74.28 KB 您将下载一个JQuery插件资源包,该资源包内部文件的目录结构如下: 本网站提供JQuery插件下载功能,旨在帮助广大用户在工作学习中提升效率、节约时间。 本网站的下载内容来自于互联网。如您发现任何侵犯您权益的内容,请立即告知我们,我们将迅速响应并删除相关内容。 免责声明:站内所有资源仅供个人学习研究及参考之用,严禁将这些资源应用于商业场景。 若擅自商用导致的一切后果,由使用者承担责任。
2024-04-30 22:47:29
58
本站
JQuery插件下载
...能流畅地进行图片裁剪操作。该插件允许用户通过简单的拖拽、缩放和旋转等交互方式来精确裁剪上传或已存在的图片,同时具备放大缩小功能以确保细节调整的精准性。Cropper利用HTML5Canvas技术实现高性能渲染,确保了在不同浏览器下的兼容性和性能表现。此外,Cropper能够生成符合跨浏览器标准的数据,便于与后端服务集成,将用户裁剪后的图片内容无缝保存至服务器或者数据库中。因其强大的功能集和灵活的API设计,Cropper成为了众多Web应用中不可或缺的前端图片处理工具,广泛应用于头像上传、产品编辑、设计工具等多个场景。 点我下载 文件大小:490.55 KB 您将下载一个JQuery插件资源包,该资源包内部文件的目录结构如下: 本网站提供JQuery插件下载功能,旨在帮助广大用户在工作学习中提升效率、节约时间。 本网站的下载内容来自于互联网。如您发现任何侵犯您权益的内容,请立即告知我们,我们将迅速响应并删除相关内容。 免责声明:站内所有资源仅供个人学习研究及参考之用,严禁将这些资源应用于商业场景。 若擅自商用导致的一切后果,由使用者承担责任。
2023-08-08 23:21:57
310
本站
JQuery插件下载
...的回调函数和其他实用方法,这使得开发者可以根据具体需求定制化下拉列表的行为逻辑,如响应用户的操作事件,处理数据提交等。总之,这款插件是构建复杂Web应用时对多选下拉菜单高效、灵活控制的理想工具。 点我下载 文件大小:93.67 KB 您将下载一个JQuery插件资源包,该资源包内部文件的目录结构如下: 本网站提供JQuery插件下载功能,旨在帮助广大用户在工作学习中提升效率、节约时间。 本网站的下载内容来自于互联网。如您发现任何侵犯您权益的内容,请立即告知我们,我们将迅速响应并删除相关内容。 免责声明:站内所有资源仅供个人学习研究及参考之用,严禁将这些资源应用于商业场景。 若擅自商用导致的一切后果,由使用者承担责任。
2023-12-18 23:19:30
139
本站
JQuery插件下载
...转换为视觉效果更佳、操作更为直观的滑动开关按钮。通过集成这款插件,开发者能够轻松实现对表单元素样式的高级定制,无需繁琐的手动CSS或JavaScript编写。该插件设计灵活且功能丰富,预设了四种不同的主题风格,包括light(明亮)、dark(暗黑)、flat(扁平)以及modern(现代),只需在调用时简单配置参数,即可快速切换至所需的样式主题,让滑动开关与整体页面设计完美融合。使用"rcSwitcher"不仅增强了网站的视觉吸引力,还优化了移动端和平板设备上的触控交互,使得用户在操作选择项时更加便捷流畅。无论是进行设置项的选择、状态的切换或是表单数据的填写,都能带给用户新颖而友好的互动感受,提升了网站的专业性和用户体验。 点我下载 文件大小:49.23 KB 您将下载一个JQuery插件资源包,该资源包内部文件的目录结构如下: 本网站提供JQuery插件下载功能,旨在帮助广大用户在工作学习中提升效率、节约时间。 本网站的下载内容来自于互联网。如您发现任何侵犯您权益的内容,请立即告知我们,我们将迅速响应并删除相关内容。 免责声明:站内所有资源仅供个人学习研究及参考之用,严禁将这些资源应用于商业场景。 若擅自商用导致的一切后果,由使用者承担责任。
2023-05-21 23:51:34
333
本站
JQuery插件下载
...功能完美结合,让网页数据展示更加直观高效。使用StickySort,你可以轻松创建出具有固定表头的表格,即使用户滚动页面,表头始终可见,确保了数据上下文的清晰性。此外,每个表格列都支持点击排序,用户可以根据需要快速切换列的升序或降序排列。无论是用于展示产品列表、用户信息还是数据分析,StickySort都能提供卓越的用户体验。其简单易用的特点使得开发者无需深入了解复杂的前端技术,只需几行代码即可实现这些高级功能。对于需要处理大量数据的网站,StickySort无疑是一个理想的选择,它不仅提升了界面美观度,还极大增强了数据的可读性和操作便捷性。总之,StickySort凭借其出色的性能和友好的用户界面,成为了现代Web开发中不可或缺的工具之一。无论是个人项目还是企业级应用,StickySort都能为你带来前所未有的表格交互体验。 点我下载 文件大小:60.60 KB 您将下载一个JQuery插件资源包,该资源包内部文件的目录结构如下: 本网站提供JQuery插件下载功能,旨在帮助广大用户在工作学习中提升效率、节约时间。 本网站的下载内容来自于互联网。如您发现任何侵犯您权益的内容,请立即告知我们,我们将迅速响应并删除相关内容。 免责声明:站内所有资源仅供个人学习研究及参考之用,严禁将这些资源应用于商业场景。 若擅自商用导致的一切后果,由使用者承担责任。
2025-02-14 21:06:04
24
本站
JQuery插件下载
...s来增强日期和时间的操作能力。这款插件允许用户在网页中实现直观且功能丰富的日期选择体验,支持自定义日期范围、起始日期等多种配置选项。通过集成moment.js,插件能够高效地解析用户输入的日期格式,并进行有效验证与格式化输出,从而确保数据一致性。开发者可以根据项目需求选择使用ES5或ES6版本的插件代码,以适应不同环境下的JavaScript开发标准。在实际应用中,只需简单地在HTML文档中引入jQuery、moment.js以及该日期选择器所需的CSS和JS文件,然后通过调用DatePicker()方法初始化插件,即可轻松创建一个功能完备的日期选择界面,例如设置初始日期为当前日期:$('singleDateRange').DatePicker({startDate:moment()});总之,此插件结合了jQuery的良好DOM操作特性和moment.js的强大日期时间处理能力,提供了一种灵活易用且功能强大的解决方案,广泛适用于各种需要日期选择功能的Web应用程序中。 点我下载 文件大小:51.51 KB 您将下载一个JQuery插件资源包,该资源包内部文件的目录结构如下: 本网站提供JQuery插件下载功能,旨在帮助广大用户在工作学习中提升效率、节约时间。 本网站的下载内容来自于互联网。如您发现任何侵犯您权益的内容,请立即告知我们,我们将迅速响应并删除相关内容。 免责声明:站内所有资源仅供个人学习研究及参考之用,严禁将这些资源应用于商业场景。 若擅自商用导致的一切后果,由使用者承担责任。
2024-04-21 23:21:13
371
本站
JQuery插件下载
...持全选、全不选等快捷操作。其界面设计友好,即使面对大量选项也能轻松管理,大大提升了用户体验和工作效率。开发者使用时只需引入jQuery以及该插件所需的CSS和JS文件,并对目标元素调用.multiSelect()方法进行初始化配置,即可实现多选下拉框功能。此外,该插件还兼容数据回显,能够根据后端返回的数据动态渲染已选中的选项,满足各种复杂业务需求。总之,jQuery多选下拉框插件是一个高效实用的前端工具,它极大地扩展了原生HTML表单控件的功能,使得多选功能在视觉与交互层面都得到了显著提升,是现代Web开发中优化表单组件的理想选择。 点我下载 文件大小:77.65 KB 您将下载一个JQuery插件资源包,该资源包内部文件的目录结构如下: 本网站提供JQuery插件下载功能,旨在帮助广大用户在工作学习中提升效率、节约时间。 本网站的下载内容来自于互联网。如您发现任何侵犯您权益的内容,请立即告知我们,我们将迅速响应并删除相关内容。 免责声明:站内所有资源仅供个人学习研究及参考之用,严禁将这些资源应用于商业场景。 若擅自商用导致的一切后果,由使用者承担责任。
2023-09-29 20:51:12
128
本站
JQuery插件下载
...uery强大的DOM操作和事件处理能力,实现了一种界面友好且高度灵活的选项卡功能。该插件尤其适用于需要在不同屏幕尺寸下保持良好展示效果的响应式布局设计,能够根据容器宽度自动调整其尺寸及布局,确保在桌面、平板和手机等各类设备上均能提供流畅且一致的用户体验。开发者可以轻松地将此插件应用于网站内容区域以组织并切换不同的内容面板,如产品详情、文章概览或用户反馈等。只需通过简单的HTML结构标记各个选项卡标题和对应的内容面板,再调用相应的jQuery方法即可初始化选项卡功能。此外,该插件还特别强调样式定制的便捷性,设计师可以根据项目需求完全自定义选项卡的视觉样式,只需要编写CSS代码来覆盖默认样式,从而与整体网站主题风格保持统一。总之,“简单响应式jQueryTabs选项卡插件”以其易用性、响应式特性和高度可定制化的特点,成为网页开发者构建动态交互界面的理想工具之一。 点我下载 文件大小:48.30 KB 您将下载一个JQuery插件资源包,该资源包内部文件的目录结构如下: 本网站提供JQuery插件下载功能,旨在帮助广大用户在工作学习中提升效率、节约时间。 本网站的下载内容来自于互联网。如您发现任何侵犯您权益的内容,请立即告知我们,我们将迅速响应并删除相关内容。 免责声明:站内所有资源仅供个人学习研究及参考之用,严禁将这些资源应用于商业场景。 若擅自商用导致的一切后果,由使用者承担责任。
2024-04-02 12:08:29
359
本站
JQuery插件下载
...放置功能,无论是鼠标操作还是手指触摸交互都能流畅响应。该插件的核心优势在于其良好的兼容性和高度定制性,允许用户轻松调整、交换或重新排序页面上的各个组件。在移动端,Dragula充分利用触摸事件模型,确保用户可以通过手指滑动屏幕进行直观且自然的拖拽操作,从而提升移动用户的使用体验。无需复杂的配置过程,只需引入Dragula库并调用相应方法,即可使任何HTML元素变得可拖动,并在拖放到目标区域时自动更新布局。由于其小巧的体积和零外部依赖特性,Dragula深受开发者喜爱,适用于构建各种需要拖放功能的Web应用程序,从简单的列表项排序到复杂的数据可视化布局调整等场景。 点我下载 文件大小:27.63 KB 您将下载一个JQuery插件资源包,该资源包内部文件的目录结构如下: 本网站提供JQuery插件下载功能,旨在帮助广大用户在工作学习中提升效率、节约时间。 本网站的下载内容来自于互联网。如您发现任何侵犯您权益的内容,请立即告知我们,我们将迅速响应并删除相关内容。 免责声明:站内所有资源仅供个人学习研究及参考之用,严禁将这些资源应用于商业场景。 若擅自商用导致的一切后果,由使用者承担责任。
2023-11-12 23:58:06
62
本站
JQuery插件下载
...,支持各种常见的日期操作任务,包括但不限于日期解析、格式化、添加或减去时间单位、比较日期等。无论你是需要将日期转换成特定格式,还是进行复杂的日期计算,Day.js都能轻松应对。此外,它还提供了丰富的插件系统,允许用户根据需要扩展其功能,满足更复杂的应用场景需求。对于熟悉Moment.js的开发者而言,使用Day.js几乎不需要额外的学习成本。你只需将现有的Moment.js代码稍作调整即可无缝切换至Day.js,享受其带来的高性能与低资源消耗。对于新手来说,Day.js同样易于上手,文档详尽且示例丰富,可以帮助快速掌握使用方法。总之,如果你正在寻找一款高效、灵活且易于集成的时间和日期处理解决方案,那么Day.js将是你的理想选择。它不仅能满足日常开发中的基本需求,还能通过插件机制轻松应对更多高级用例。无论是构建个人项目还是企业级应用,Day.js都是处理时间和日期问题时不可或缺的强大工具。 点我下载 文件大小:154.16 KB 您将下载一个JQuery插件资源包,该资源包内部文件的目录结构如下: 本网站提供JQuery插件下载功能,旨在帮助广大用户在工作学习中提升效率、节约时间。 本网站的下载内容来自于互联网。如您发现任何侵犯您权益的内容,请立即告知我们,我们将迅速响应并删除相关内容。 免责声明:站内所有资源仅供个人学习研究及参考之用,严禁将这些资源应用于商业场景。 若擅自商用导致的一切后果,由使用者承担责任。
2024-12-15 10:56:28
92
本站
JQuery插件下载
...调函数允许用户在页面切换时执行特定的操作,比如更新数据或加载新的内容,极大地提升了用户体验。jquery.tabulation.js不仅代码轻量,而且运行流畅,可以显著提升网站性能。此外,其简洁明了的文档和丰富的示例代码使得即使是初学者也能快速上手,轻松集成到现有项目中。总之,jquery.tabulation.js是任何希望为其网站添加美观且实用分页功能的开发者不可或缺的工具。无论是为了提高用户体验,还是优化网站结构,这款插件都是您的理想选择。 点我下载 文件大小:46.31 KB 您将下载一个JQuery插件资源包,该资源包内部文件的目录结构如下: 本网站提供JQuery插件下载功能,旨在帮助广大用户在工作学习中提升效率、节约时间。 本网站的下载内容来自于互联网。如您发现任何侵犯您权益的内容,请立即告知我们,我们将迅速响应并删除相关内容。 免责声明:站内所有资源仅供个人学习研究及参考之用,严禁将这些资源应用于商业场景。 若擅自商用导致的一切后果,由使用者承担责任。
2025-01-12 21:13:23
101
本站
JQuery插件下载
...Select支持键盘操作,这意味着用户可以通过上下键导航列表项,使用Enter键选择项目,或者使用Tab和Esc键进行快速切换和退出。这种键盘驱动的设计特别适合移动设备和平板电脑,因为它们通常依赖于触摸屏输入,而键盘操作则提供了更高效的交互方式。其次,插件还内置了搜索功能,允许用户在列表中快速查找特定选项。当用户开始输入文本时,Combo-Select会实时更新显示结果,帮助用户快速定位所需的选项。这不仅提高了搜索效率,也极大地提升了用户的满意度。此外,Combo-Select在移动数据和平板电脑环境下自动退化为原生的select元素,确保在所有设备上的兼容性和可用性。这种设计考虑到了不同设备和浏览器的差异,使得插件能够在广泛的环境中提供一致的用户体验。综上所述,jQuery插件Combo-Select通过其强大的键盘控制、内置搜索功能以及对不同设备的适应性,为开发者提供了一个高效、灵活且易于集成的解决方案。它不仅提升了网页交互的流畅度,也增强了用户在选择和搜索选项时的便利性,是构建现代、响应式Web应用的理想选择。 点我下载 文件大小:50.32 KB 您将下载一个JQuery插件资源包,该资源包内部文件的目录结构如下: 本网站提供JQuery插件下载功能,旨在帮助广大用户在工作学习中提升效率、节约时间。 本网站的下载内容来自于互联网。如您发现任何侵犯您权益的内容,请立即告知我们,我们将迅速响应并删除相关内容。 免责声明:站内所有资源仅供个人学习研究及参考之用,严禁将这些资源应用于商业场景。 若擅自商用导致的一切后果,由使用者承担责任。
2024-09-20 11:12:17
51
本站
JQuery插件下载
...名为“页面过渡、页面切换、ajax”,提供了一种令人眼前一亮的网站交互体验。通过利用现代浏览器的强大功能,此插件能够实现网页内容的无缝切换,同时加入华丽的CSS3动画效果,使整个页面过渡过程流畅自然,视觉效果震撼。在用户点击链接时,插件会运用AJAX技术异步请求服务器数据,避免了传统页面刷新导致的等待时间,从而大大提升了用户体验。与此同时,借助CSS3强大的动画能力,每一个页面切换动作都如同精心设计的电影场景般流畅,从淡入淡出到旋转翻页,各种动画效果可供选择,让每一次页面转换都成为一次视觉盛宴。不仅如此,这款插件还支持自定义动画效果,开发者可以根据实际需求调整动画速度、持续时间和触发条件等参数,使得页面过渡更加符合网站的整体风格和品牌调性。无论是用于展示作品集、博客文章还是企业官网,这款插件都能帮助你打造出既有深度又具吸引力的互动式网站。总之,这款“页面过渡、页面切换、ajax”插件不仅简化了网站开发过程中的复杂操作,更以其独特的动画效果和出色的用户体验,成为了网页设计师和前端开发者不可或缺的工具之一。 点我下载 文件大小:51.66 KB 您将下载一个JQuery插件资源包,该资源包内部文件的目录结构如下: 本网站提供JQuery插件下载功能,旨在帮助广大用户在工作学习中提升效率、节约时间。 本网站的下载内容来自于互联网。如您发现任何侵犯您权益的内容,请立即告知我们,我们将迅速响应并删除相关内容。 免责声明:站内所有资源仅供个人学习研究及参考之用,严禁将这些资源应用于商业场景。 若擅自商用导致的一切后果,由使用者承担责任。
2024-10-26 20:53:05
58
本站
JQuery插件下载
....触摸友好:针对触屏操作进行了优化,滑动、点击和缩放都极其流畅,确保了在移动设备上的良好互动体验。3.速度与效率:通过优化的代码实现快速加载,减少延迟,提升网站性能,即使在数据加载较大的云存储情况下也能保持流畅。4.多功能性:支持幻灯片模式、全屏显示以及分页导航,便于展示一系列相关图像或内容。5.懒加载:智能地只在用户需要时加载图片,节省带宽,提高首屏加载速度。6.Bootstrap兼容:与流行的前端框架Bootstrap无缝集成,简化了集成过程,提高了开发效率。7.多语言与定制:提供了多语言选项,方便全球用户,同时允许用户根据个人喜好定制外观和行为。8.丰富的主题:包含多种预设主题,可以轻松切换以匹配网站整体风格,满足不同设计需求。总之,这款jQuerylightbox插件不仅能满足基本的图片放大功能,还通过其全面的特性,为现代网站提供了出色的用户体验和性能,是构建现代响应式网站的理想选择。 点我下载 文件大小:512.50 KB 您将下载一个JQuery插件资源包,该资源包内部文件的目录结构如下: 本网站提供JQuery插件下载功能,旨在帮助广大用户在工作学习中提升效率、节约时间。 本网站的下载内容来自于互联网。如您发现任何侵犯您权益的内容,请立即告知我们,我们将迅速响应并删除相关内容。 免责声明:站内所有资源仅供个人学习研究及参考之用,严禁将这些资源应用于商业场景。 若擅自商用导致的一切后果,由使用者承担责任。
2024-05-19 15:43:58
100
本站
JQuery插件下载
...户对某列标题进行点击操作时,该列会响应性地展开,揭示隐藏的详细信息。这一功能不仅增强了数据的可访问性,还让关键信息在需要时一目了然,无需用户在多个页面或窗口间切换。通过这种交互模式,用户能够更加专注于所需的数据,提高工作效率和满意度。此插件的设计遵循了Bootstrap框架,确保了与现代Web标准的兼容性,并能适应各种设备屏幕尺寸,包括桌面、平板和手机。其简洁的API使得集成到现有的jQuery项目中变得异常容易,开发者只需几行代码即可启用强大的列展开功能。此外,“可展开列的jQuery表格插件”支持多种定制选项,包括颜色、字体大小和样式调整,以满足不同网站设计的需求。这意味着开发人员可以根据品牌形象或特定场景灵活调整表格外观,使其既符合功能需求,又能融入整体设计风格。总之,这款插件是动态表格应用的理想选择,它结合了高效的数据展示、用户友好性和高度可定制性,为网站或应用程序提供了增强用户体验的关键功能。通过简单而强大的交互机制,它能够显著提升数据密集型页面的可用性和吸引力。 点我下载 文件大小:50.10 KB 您将下载一个JQuery插件资源包,该资源包内部文件的目录结构如下: 本网站提供JQuery插件下载功能,旨在帮助广大用户在工作学习中提升效率、节约时间。 本网站的下载内容来自于互联网。如您发现任何侵犯您权益的内容,请立即告知我们,我们将迅速响应并删除相关内容。 免责声明:站内所有资源仅供个人学习研究及参考之用,严禁将这些资源应用于商业场景。 若擅自商用导致的一切后果,由使用者承担责任。
2024-08-04 20:40:35
304
本站
JQuery插件下载
...件的正确性,避免因误操作导致的数据损失。拖拽上传该插件引入了拖拽上传功能,允许用户通过简单地将文件从桌面或其他文件管理器中拖放到上传区域来完成文件上传。这种交互方式更加自然流畅,减少了用户的操作步骤,提升了使用效率。AJAX技术支持ssi-uploader采用AJAX技术进行后台数据交换,实现了无刷新页面的上传过程。这意味着用户可以在上传文件的同时继续浏览网站其他部分的内容,无需等待上传完成页面重新加载,大大提升了用户体验。文件格式与大小控制插件提供了对上传文件格式和大小的严格控制。开发者可以根据需要设置允许上传的文件类型及其最大容量,有效防止恶意文件的上传,同时保护服务器资源不被滥用。回调函数ssi-uploader支持多种回调函数,允许开发者根据上传进度或结果执行特定操作。例如,可以设置成功上传后的处理逻辑,如发送邮件通知、更新数据库等,极大地增强了插件的灵活性和实用性。使用便利性总体而言,ssi-uploader以其丰富的功能和便捷的使用方式,成为了前端文件上传领域的佼佼者。无论是个人项目还是企业应用,只要涉及文件上传需求,都可以考虑使用此插件,以提升用户界面的友好性和功能性。 点我下载 文件大小:122.78 KB 您将下载一个JQuery插件资源包,该资源包内部文件的目录结构如下: 本网站提供JQuery插件下载功能,旨在帮助广大用户在工作学习中提升效率、节约时间。 本网站的下载内容来自于互联网。如您发现任何侵犯您权益的内容,请立即告知我们,我们将迅速响应并删除相关内容。 免责声明:站内所有资源仅供个人学习研究及参考之用,严禁将这些资源应用于商业场景。 若擅自商用导致的一切后果,由使用者承担责任。
2024-09-27 20:51:51
92
本站
站内搜索
用于搜索本网站内部文章,支持栏目切换。
知识学习
实践的时候请根据实际情况谨慎操作。
随机学习一条linux命令:
cut -d ',' -f 1,3 file.csv
- 根据逗号分隔符提取csv文件中第1列和第3列的内容。
推荐内容
推荐本栏目内的其它文章,看看还有哪些文章让你感兴趣。
2023-04-28
2023-08-09
2023-06-18
2023-04-14
2023-02-18
2023-04-17
2024-01-11
2023-10-03
2023-09-09
2023-06-13
2023-08-07
2023-03-11
历史内容
快速导航到对应月份的历史文章列表。
随便看看
拉到页底了吧,随便看看还有哪些文章你可能感兴趣。
时光飞逝
"流光容易把人抛,红了樱桃,绿了芭蕉。"