一条SQL语句查出每个班的及格人数和不及格人数,格式为:class,及格人数,不及格人数(score>=60为及格)

题目描述:

现有表 tb1 ,有字段  name, class, score .分别代表 姓名,所在班级,分数。

要求:用一条SQL语句查询出每个班的及格人数和不及格人数,格式为:class,及格人数,不及格人数(score>=60为及格)

解答:

select class 班级,

sum(case when score>=60 then 1 else 0 end) as 及格人数,
                  sum(case when score<60 then 1 else 0 end) as 不及格人数
        from tb1
        group by class;

比特币BTC支付API接口中文文档

随着经济全球化和数字经济的迅猛发展,数字资产早已变成了投资客积累财富的重要标志。对于长线存储虚拟货币的交易所、项目方、个人来说,钱包系统成为存储、管理数字资产、发展业务不可或缺的工具。

接踵而来的是,行业竞争愈演愈烈,全球区块链钱包如雨后春笋般涌现,产品数量达近千种。

一个匿名的诞生神话,一个爆炸性的愿景,不可否认的吸引力,一个由数学治理的全新经济模型——比特币作为虚拟货币的开山鼻祖,也是当前占据市值和流量之王,更是交易中的核心币种,钱包的价值存在,在数字货币市场中占据着主导作用。

比特币在交易所市场中的活跃,带动了交易所、项目方对于比特币支付API接口的需求。

如何理解API?

常见解释:API,英文可翻译为应用程序编程接口。指一些预先定义的函数,为应用程序与开发人员提供基于某软件或硬件得以访问一组例程的能力,但又不需要访问源代码,或者去了解内部工作机制的细节。

如果上述解释比较晦涩难懂,不妨用一个小故事来理解:

开发团队A开发了软件A,开发团队 B正在研发软件B。某一天,开发团队B打算调用软件A的部分功能来用,但是他们又不想从头看一遍软件A的源码和功能实现过程,怎么办呢?

开发团队A想到了个好办法:我们把软件A里你需要的功能打包好,写成一个函数。你按照我说的流程,把这个函数放在软件B里,就能直接用我的功能了!

在这里,API指的就是开发团队A说的那个函数。

什么是支付接口?

意指一个集成的开发接口程序,接口能够包含各种通道,对接后,可实现多种支付通道功能。

比如全球首款区块链交易所钱包对接开放平台,优盾钱包,提供比特币_以太坊_USDT_EOS_XRP等主流erc20代币对接交易所钱包充提币、转账支付归集API/RPC的php/java接口开发解决方案。

比特币BTC支付API接口中文文档

详细的比特币支付API接口文档如下:

原文链接:https://www.uduncloud.com/gateway-interface

1、目录

1.1、生成地址

1.2、提币

1.3、代付

1.4、交易回调

1.5、校验地址合法性

1.6、获取商户支持币种信息

2、接口明细

1、生成地址

1.1 场景说明

请求指定币种地址,如要成功获取地址,需先存在钱包,且钱包支持该币种, 详情参看

1.2 接口详情

1.2.1 接口地址

接口详情
URL【/mch/address/create】
请求方式POST

1.2.2 参数

1.2.2.1 参数说明

参数类型是否必填说明备注
timestampString时间戳见 验签说明
nonceString随机数见 验签说明
signString签名见 验签说明
bodyString消息内容json字符串,格式如下

[

    {

     “merchantId”:”300015″,

     “coinType”:520,

     “callUrl”:”http://localhost:8080/callBack”

    }

]

1.2.2.2 body参数字段

body参数名类型是否必填说明
merchantIdString商户号
coinTypeInteger主币种编号,见 附录一
callUrlString回调地址,通过该接口创建的地址,以后关于该地址的充币信息会通过您指定的回调地址通知您。具体示例见 交易回调接口
walletIdString钱包编号,默认根据主钱包生成地址
aliasString地址别名

1.2.2.3 示例

{

    “timestamp”: 1535005047,

    “nonce”: 10000,

    “sign”: “a230def43c1a12b14393880a28d4e005”,

    “body”: “[{\”merchantId\”:\”300015\”,\”coinType\”:520,\”callUrl\”:\”http://localhost:8080/callBack\”}]” 

}

1.2.3 返回状态码表

code解释
200成功
4005非法参数
4001商户不存在
4169商户已禁用
4162签名错误
4175钱包编号错误
4017商户没有创建钱包
4176钱包未添加支持该币种
4166商户没有配置套餐
4168商户地址达到上限
4045币种信息错误
-1获取地址失败

1.3 调取示例

1.3.1 成功

{

    “data”:{

        “coinType”:520,

        “address”:”0xbe4e3699cb870bc95365fe04a187dd279a651a58″

    },

    “message”:”SUCCESS”,

    “code”:200

}

1.3.2 失败

{

    “code”: “4101”,

    “message”: “SIGN_MSG_ERROR”

}

2、发送提币申请

2.1 场景说明

提币申请

2.2 接口详情

2.2.1 接口地址

接口详情
URL【/mch/withdraw】
请求方式POST

2.2.2 参数

2.2.2.1 参数说明

参数类型是否必填说明备注
timestampString时间戳见 验签说明
nonceString随机数见 验签说明
signString签名见 验签说明
bodyString消息内容json字符串,格式如下

[

    {

        “address”:”raadSxrUhG5EQVCY75CSGaVLWCeXd6yH6s”,

        “amount”:”0.11″,

        “merchantId”:”100109″,

        “mainCoinType”:”144″,

        “coinType”:”144″,

        “callUrl”:”http://localhost:8080/mch/callBack”,

        “businessId”:”15″,

        “memo”:”10112″

    }

]

2.2.2.2 body参数字段

body参数名称是否必填类型说明
addressString提币地址
amountString提币数量
merchantIdString商户号
mainCoinTypeString主币种编号 (见 附录一 )
coinTypeString子币种编号 (见 附录一 )
callUrlString回调地址,通过该callUrl告知您该笔提币交易的状态,具体示例见 交易回调接口
businessIdString业务id,必须保证该字段在系统内唯一,如果重复,则该笔审核钱包不会接收。
memoString备注,XRP和EOS,这两种币的提币申请该字段可选,起他类型币种不填

2.2.2.3 示例

{

  “timestamp”: 1535005047,

  “nonce”: 100000,

  “sign”: “6df1512ee650431632ce1541a6b064e1”,

  “body”: “[{\”address\”:\”raadSxrUhG5EQVCY75CSGaVLWCeXd6yH6s\”,\”amount\”:\”0.11\”,\”merchantId\”:\”100109\”,\”mainCoinType\”:\”144\”,\”coinType\”:\”144\”,\”callUrl\”:\”http://localhost:8080/callBack\”,\”businessId\”:\”15\”,\”memo\”:\”10112\”}]” 

}

2.2.3 返回状态码表

code解释
200成功
4005非法参数
4598传入body中的list对象中的所有merchantId必须保持一致
4001商户不存在
4169商户已被禁用
4183到账地址异常
4193EOS金额小数点后超过4位长度
4034未找到该币种信息

2.3.1 成功

{

    “message”:”SUCCESS”,

    “code”:200

}

2.3.2 失败

{

    “code”: “4101”,

    “message”: “SIGN_MSG_ERROR”

}

3、代付

3.1 场景说明

代付,发送自动付款申请,未设置代付信息或代付失败则进入审核状态。

3.2 接口详情

3.2.1 接口地址

接口详情
URL【/mch/withdraw/proxypay】
请求方式POST

3.2.2 参数

3.2.2.1 参数说明

参数类型是否必填说明备注
timestampString时间戳见 验签说明
nonceString随机数见 验签说明
signString签名见 验签说明
bodyString消息内容JSON字符串,格式如下

[

  {

      “address”:”raadSxrUhG5EQVCY75CSGaVLWCeXd6yH6s”,

      “amount”:”0.1″,

      “merchantId”:”100146″,

      “mainCoinType”:”144″,

      “coinType”:”144″,

      “callUrl”:”http://localhost:8080/callBack”,

      “businessId”:”571001″,

      “memo”:”10112″

  }

]

3.2.2.2 body参数说明

body参数名称类型是否必填说明
merchantIdString商户号
addressString提币地址
mainCoinTypeString主币种编号,见 附录一
coinTypeString子币种编号,见 附录一
amountString交易数量
callUrlString回调地址,提币(审核、交易)结果将通过该地址进行回调,具体示例见 交易回调接口
businessIdString业务id,必须保证该字段在系统内唯一,如果重复,则该笔提币钱包将不会进行接收
memoString备注,XRP和EOS,这两种币的提币申请该字段可选,起他类型币种不填

3.2.2.2 示例

{

    “timestamp”: 1535005047,

    “nonce”: 100000,

    “sign”: “e1bee3a417b9c606ba6cedda26db761a”,

    “body”: “[{\”address\”:\”raadSxrUhG5EQVCY75CSGaVLWCeXd6yH6s\”,\”amount\”:\”0.1\”,\”merchantId\”:\”100146\”,\”mainCoinType\”:\”144\”,\”coinType\”:\”144\”,\”callUrl\”:\”http://localhost:8080/callBack\”,\”businessId\”:\”571001\”,\”memo\”:\”10112\”}]”

}

3.2.3 返回状态码表

code解释
200成功
4005非法参数
4001商户不存在
4166商户没有配置套餐
4169商户已被禁用
4612签名错误
4163签名信息错误
569无效的地址
571已存在审核记录,将不再进行处理
581非法提币金额
554商户不支持该币种

3.3 调取示例

3.3.1 成功

{

    “message”:”SUCCESS”,

    “code”:200

}

3.3.2 失败

{

    “code”: “4101”,

    “message”: “SIGN_MSG_ERROR”

}

4、交易回调接口

4.1 场景说明

网关收到交易处理结果,调用商户提供的回调接口,通知商户具体变化信息。该接口网关发送给您指定的回调地址的内容,处理您的业务信息。 分充值回调和提币回调,其中提币最多会进行两次回调(审核回调+交易结果回调)

4.2 接口详情

4.2.1 接口地址

接口详情
URL 
请求方式POST

4.2.2 参数

4.2.2.1 参数说明

参数类型是否必填说明备注
timestampString时间戳见 验签说明
nonceString随机数见 验签说明
signString签名见 验签说明
bodyString消息内容JSON字符串,格式如下

{

    “address”:”DJY781Z8qbuJeuA7C3McYivbX8kmAUXPsW”,

    “amount”:”12345678″,

    “blockHigh”:”102419″,

    “coinType”:”206″,

    “decimals”:”8″,

    “fee”:”452000″,

    “mainCoinType”:”206″,

    “status”:3,

    “tradeId”:”20181024175416907″,

    “tradeType”:1,

    “txId”:”31689c332536b56a2246347e206fbed2d04d461a3d668c4c1de32a75a8d436f0″,

    “businessId”:””,// 提币回调为提币接口传入的businessId,充币无值

    “memo”:””

}

4.2.2.2 body参数说明

body参数名称类型说明
addressString地址
amountString交易数量,根据币种精度获取实际金额,实际金额=amount/pow(10,decimals),即实际金额等于amount除以10的decimals次方
feeString矿工费,根据币种精度获取实际金额,实际金额获取同上
decimalsString币种精度
coinTypeString子币种编号,见 附录一
mainCoinTypeString主币种编号,见 附录一
businessIdString业务编号,提币回调时为提币请求时传入的,充币回调无值
blockHighString区块高度
statusInteger状态,见 回调接口状态说明
tradeIdString交易流水号
tradeTypeInteger交易类型,见 回调接口交易类型说明
txidString区块链交易哈希
memoString备注,XRP和EOS(见 附录一 ),这2种类型币的充提币可能有值

4.2.2.2 示例

{

    “timestamp”: 1535005047,

    “nonce”: 100000,

    “sign”: “e1bee3a417b9c606ba6cedda26db761a”,

    “body”: “{\”address\”:\”DJY781Z8qbuJeuA7C3McYivbX8kmAUXPsW\”,\”amount\”:\”12345678\”,\”blockHigh\”:\”102419\”,\”coinType\”:\”206\”,\”decimals\”:\”8\”,\”fee\”:\”452000\”,\”mainCoinType\”:\”206\”,\”status\”:3,\”tradeId\”:\”20181024175416907\”,\”tradeType\”:1,\”txId\”:\”31689c332536b56a2246347e206fbed2d04d461a3d668c4c1de32a75a8d436f0\”}”

}

5、校验地址合法性

5.1 场景说明

校验地址的合法性,添加地址、提币申请等场景时可先校验地址合法性,参看 校验规则

5.2 接口详情

5.2.1 接口地址

接口详情
URL【/mch/check/address】
请求方式Post

5.2.2 参数

5.2.2.1 参数说明

参数类型是否必填说明备注
timestampString时间戳 
nonceString随机数 
signString签名 
bodyString消息内容JSON字符串,格式如下

{

    “merchantId”:200000,

    “mainCoinType”:”206″,

    “address”:”DJY781Z8qbuJeuA7C3McYivbX8kmAUXPsW”

}

5.2.2.2 body参数说明

body参数名称类型是否必填说明
merchantIdLong商户号
mainCoinTypeString主币种编号,见 附录一
addressString需校验的地址

5.2.2.2 示例

{

    “timestamp”: 1535005047,

    “nonce”: 100000,

    “sign”: “e1bee3a417b9c606ba6cedda26db761a”,

    “body”: “[{\”merchantId\”:200000,\”mainCoinType\”:\”206\”,\”address\”:\”DJY781Z8qbuJeuA7C3McYivbX8kmAUXPsW\”}]”

5.2.3 返回状态码表

code解释
200成功
4005非法参数
4162签名错误
4165地址不合法

5.3 调取示例

5.3.1 成功

{

    “code”:200,

    “message”:”SUCCESS”

}

5.3.2 失败

{

    “code”:4005,

    “message”:”PARAM_ERROR”

}

6、获取商户支持的币种信息

6.1 场景说明

获取商户支持的币种,以及余额

6.2 接口详情

6.2.1 接口地址

接口详情
URL【/mch/support-coins】
请求方式POST

6.2.2 参数

6.2.2.1 参数说明

参数类型是否必填说明
timestampString时间戳
nonceString随机数
signString签名
bodyString消息内容

6.2.2.2 body参数说明

body参数名称类型是否必填说明
merchantIdLong商户号
showBalanceBoolean是否查询余额,false不获取,true获取

6.2.2.3 示例

{

    “timestamp”: 1535005047,

    “nonce”: 100000,

    “sign”: “e1bee3a417b9c606ba6cedda26db761a”,

    “body”: “{\”merchantId\”:\”200032\”,\”showBalance\”:true}”

}

6.2.3 返回状态码表

状态码解释
200成功
4005body参数错误

6.3 调取示例

6.3.1 成功

{

    “code”: 200,

    “message”: “SUCCESS”,

    “data”:[

        {

            “name”: “BTC”, // 币种别名

            “coinName”:”Bitcoin”, // 币种全称

            “symbol”:”BTC”, // 币种单位

            “mainCoinType”:”0″, //主币种类型

            “coinType”:”0″, // 币种类型

            “decimals”:”8″, // 币种精度

            “tokenStatus”:”0″, // 0: 主币 1:代币

            “mainSymbol”:”BTC”, //主币种单位

            “balance”:”0″, // 币种余额

            “logo”:”http://bipay-admin.oss-cn-hangzhou.aliyuncs.com/bipay-admin-release/coin-logo/BTC.png” // 币种log地址

        },

        {

            “name”: “ETH”, // 币种别名

            “coinName”:”Ethereum”, // 币种全称

            “symbol”:”ETH”, // 币种单位

            “mainCoinType”:”60″, //主币种类型

            “coinType”:”60″, // 币种类型

            “decimals”:”18″, // 币种精度

            “tokenStatus”:”0″, // 0: 主币 1:代币

            “mainSymbol”:”ETH”, //主币种单位

            “balance”:”0″, // 币种余额

            “logo”:”https://bipay-admin.oss-cn-hangzhou.aliyuncs.com/bipay-admin-release/coin-logo/ETH.png” // 币种log地址

        }

    ]

}

6.3.2 失败

{

    “code”:4005,

    “message”:”BGS_ILLEGAL_PARAMETER”

}

附录一

主币种编号子币种编号币种简称币种英文名币种中文名称精度
00BTCBitcoin比特币8
6060ETHEthereum以太坊18
031USDTTether USD泰达币8
520520CNTCNT测试币18
55DASHDASH达世币8
133133ZECZEC大零币8
145145BCHBitcoincash比特币现金8
6161ETCEthereum Classic以太坊经典18
22LTCLTC莱特币8
23012301QTUMQTUM量子链币8
502502GCCGalaxyChain 8
60合约地址eth代币eth代币 根据代币具体情况而定
144144XRPRipple瑞波币6
194194EOSEOS柚子币4
194194EOSEOS柚子币4
23042304IOTEIOTEIOTE8
23032303VDSVollarVollar币8

回调接口状态说明

状态说明
0待审核
1审核成功
2审核驳回
3交易成功
4交易失败

回调接口交易类型说明

状态说明
1充币回调
2提币回调

验签说明

为了保证商户传送到优盾的参数信息不被恶意篡改,网关为商户接口提供Md5加密摘要认证。商户可用基础加密参数:时间戳、随机数、签名密钥(商户唯一的APIKEY)、请求明文参数按指定顺序排列进行Md5加密,产生一个验签串sign,商户请求网关接口时,带上参数时间戳、随机数、请求明文参数、sign作为参数。网关拿到相应的参数以同样的方式进行签名验签。同理,网关请求商户也以同样的方式进行身份验证。

sign=md5(body + key + nonce + timestamp)

key为接口授权码APIKEY,由网关分配给商户,加密字段顺序不能错误

币种地址校验规则

主币种类型币种简称币种英文名称币种中文名称地址前缀地址长度限制区间
0BTCBitcoin比特币1或者3[26,36]
60ETHEthereum以太坊0x[42]
145BCHBitcoincash比特币现金1[26,36]
61ETCEthereumClassic以太坊经典0x[42]
2LTCLitecoin莱特币L或者M[26,36]
508GXGX G[26,36]
503NBTCNBTC N不限制
99STOSTO证券型通证发行S不限制
5DASHDASH达世币X[26,36]
2301QTUMQTUM量子链币Q[26,36]
133ZECZCash大零币t1不限制
144XRPRipple瑞波币r[34]

第三方比特币支付接口的出现,告别了繁琐冗杂的支付通道接入流程,为多种通道提供了支付接口集成,将数字资产管理与业务相结合, 帮助企业快速实现数字资产接入。

比特币api大全

在开发比特币应用时,除了使用自己搭建的节点,也可以利用第三方提供的比特币api,来获取市场行情、进行交易支付、查询账户余额等。这些第三方api不一定遵循标准的比特币rpc接口规范,但往往会利用自身的数据存储来增加比特币行情api、交易到账通知api、比特币rest api等,因此可以作为比特币应用开发的有益补充。本文介绍比特币开发人员常用的第三方比特币api的特点及访问地址。

如果要快速掌握比特币的对接与应用开发,推荐汇智网的在线互动课程:

Java比特币开发详解 —–
Php比特币开发详解 —–
C#比特币开发详解

1、blockchain.com比特币api

blockchain.com的比特币api是最受欢迎的比特币开发第三方api之一,提供支付处理、钱包服务、市场行情数据等功能。blockchain.com的比特币api同时还提供了针对多种语言的封装开发包,例如python、java、.net(c#)、ruby、php和node。

地址:https://www.blockchain.com/api

2、chain.so比特币api

chain.so的特色是除了提供比特币api,还额外提供的一些山寨币的api,例如莱特币、达世币等。

chian.so的比特币api,提供了获取地址、区块、市场行情等方面的功能,也支持交易广播。免费用户有5次请求/秒的限流。

地址:https://chain.so/api

3、block.io比特币api

block.io的比特币api包括基本的钱包服务、实时通知与即时支付转发等功能,支持web hook和websocket。对于免费用户,有3次/秒的限流。

地址:https://www.block.io/docs

4、chainquery.com比特币api

chainquery.com提供了比特币rpc api的web访问接口,你可以在网页里直接输入并执行标准的比特币rpc命令。

地址:http://chainquery.com/bitcoin-api

5、coinbase.com比特币api

作为老牌的交易所,coinbase.com也提供比特币api,功能包括生成比特币地址、买/卖比特币、钱包服务、实时行情接收、支付到账通知等。

接入coinbase.com的比特币api需要使用OAuth2,这是令人不开心的一点。

地址:https://developers.coinbase.com/

6、blockcypher.com比特币api

blockcyper.com提供rest风格的比特币api,功能涵盖地址、钱包、交易等常见需求,同时提供事件和hook机制,以便应用实时得到通知。

地址: https://www.blockcypher.com/dev/bitcoin/

7、bitcoinchain.com比特币api

bitcoinchain.com提供rest和stream两种方式的比特币api,功能包括基本的比特币区块链数据交互和市场行情通知。免费用户有1次请求/秒的限流。

地址:https://bitcoinchain.com/api

8、coindesk.com比特币api

coindesk.com专注于提供比特币价格指数方面的api,包括实时BPI数据和历史BPI数据。coindesk.com的比特币api不支持与比特币区块链的交互。

地址:https://www.coindesk.com/api

9、blockchain.info比特币api

作为专业的比特币区块链浏览服务提供商,blockchain.info专注于提供比特币区块数据查询api,如果你希望查询某个地址相关的历史交易信息,bitchain.info的比特币api是最佳选择。

地址:https://blockchain.info/q

10、btc.com比特币api

btc.com的比特币api主要提供比特币区块链交易数据的查询功能,但是不支持比特币交易的广播。

地址: https://btc.com/api-doc

Java解压和压缩带密码的zip文件过程详解

这篇文章主要介绍了Java解压和压缩带密码的zip文件,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,下面我们来学习一下吧

java

前言

JDK自带的ZIP操作接口(java.util.zip包,请参看文章末尾的博客链接)并不支持密码,甚至也不支持中文文件名。

为了解决ZIP压缩文件的密码问题,在网上搜索良久,终于找到了winzipaes开源项目。

该项目在google code下托管 ,仅支持AES压缩和解压zip文件( This library only supports Win-Zip’s 256-Bit AES mode.)。网站上下载的文件是源代码,最新版本为winzipaes_src_20120416.zip,本示例就是在此基础上编写。

详述

项目使用很简单,利用源码自己导出一个jar文件,在项目中引用即可。

这里有一个需要注意的问题,就是如果给定ZIP文件没有密码,那么就不能使用该项目解压,如果压缩文件没有密码却使用该项目解压在这里会报一个异常,所以使用中需要注意:加密ZIP文件可以使用它解压,没有加密的就需要采取其它方式了。

此文就是采用修改后的winzipaes编写,并记录详细修改步骤。

winzipaes项目依赖bcprov的jar包

示例

在研究该项目时写了一个工具类,本来准备用在项目中,最后找到了更好的解决方案zip4j来代替,所以最终没有采用。

package com.ninemax.demo.zip.decrypt;import java.io.File;import java.io.IOException;import java.util.List;import java.util.zip.DataFormatException;import org.apache.commons.io.FileUtils;import de.idyl.winzipaes.AesZipFileDecrypter;import de.idyl.winzipaes.AesZipFileEncrypter;import de.idyl.winzipaes.impl.AESDecrypter;import de.idyl.winzipaes.impl.AESDecrypterBC;import de.idyl.winzipaes.impl.AESEncrypter;import de.idyl.winzipaes.impl.AESEncrypterBC;import de.idyl.winzipaes.impl.ExtZipEntry; /*** 压缩指定文件或目录为ZIP格式压缩文件* 支持中文(修改源码后)* 支持密码(仅支持256bit的AES加密解密)* 依赖bcprov项目(bcprov-jdk16-140.jar)* * @author zyh*/public class DecryptionZipUtil {    /*** 使用指定密码将给定文件或文件夹压缩成指定的输出ZIP文件* @param srcFile 需要压缩的文件或文件夹* @param destPath 输出路径* @param passwd 压缩文件使用的密码*/public static void zip(String srcFile,String destPath,String passwd) {AESEncrypter encrypter = new AESEncrypterBC();AesZipFileEncrypter zipFileEncrypter = null;try {zipFileEncrypter = new AesZipFileEncrypter(destPath, encrypter);/*** 此方法是修改源码后添加,用以支持中文文件名*/zipFileEncrypter.setEncoding("utf8");File sFile = new File(srcFile);/*** AesZipFileEncrypter提供了重载的添加Entry的方法,其中:* add(File f, String passwd) *          方法是将文件直接添加进压缩文件* * add(File f, String pathForEntry, String passwd)*          方法是按指定路径将文件添加进压缩文件* pathForEntry - to be used for addition of the file (path within zip file)*/doZip(sFile, zipFileEncrypter, "", passwd);} catch (IOException e) {e.printStackTrace();} finally {try {zipFileEncrypter.close();} catch (IOException e) {e.printStackTrace();}}} /*** 具体压缩方法,将给定文件添加进压缩文件中,并处理压缩文件中的路径* @param file 给定磁盘文件(是文件直接添加,是目录递归调用添加)* @param encrypter AesZipFileEncrypter实例,用于输出加密ZIP文件* @param pathForEntry ZIP文件中的路径* @param passwd 压缩密码* @throws IOException*/private static void doZip(File file, AesZipFileEncrypter encrypter,String pathForEntry, String passwd) throws IOException {if (file.isFile()) {pathForEntry += file.getName();encrypter.add(file, pathForEntry, passwd);return;}pathForEntry += file.getName() + File.separator;for(File subFile : file.listFiles()) {doZip(subFile, encrypter, pathForEntry, passwd);}} /*** 使用给定密码解压指定压缩文件到指定目录* @param inFile 指定Zip文件* @param outDir 解压目录* @param passwd 解压密码*/public static void unzip(String inFile, String outDir, String passwd) {File outDirectory = new File(outDir);if (!outDirectory.exists()) {outDirectory.mkdir();}AESDecrypter decrypter = new AESDecrypterBC();AesZipFileDecrypter zipDecrypter = null;try {zipDecrypter = new AesZipFileDecrypter(new File(inFile), decrypter);AesZipFileDecrypter.charset = "utf-8";/*** 得到ZIP文件中所有Entry,但此处好像与JDK里不同,目录不视为Entry* 需要创建文件夹,entry.isDirectory()方法同样不适用,不知道是不是自己使用错误* 处理文件夹问题处理可能不太好*/List<ExtZipEntry> entryList = zipDecrypter.getEntryList();for(ExtZipEntry entry : entryList) {String eName = entry.getName();String dir = eName.substring(0, eName.lastIndexOf(File.separator) + 1);File extractDir = new File(outDir, dir);if (!extractDir.exists()) {FileUtils.forceMkdir(extractDir);}/*** 抽出文件*/File extractFile = new File(outDir + File.separator + eName);zipDecrypter.extractEntry(entry, extractFile, passwd);}} catch (IOException e) {e.printStackTrace();} catch (DataFormatException e) {e.printStackTrace();} finally {try {zipDecrypter.close();} catch (IOException e) {e.printStackTrace();}}} /*** 测试* @param args*/public static void main(String[] args) {/*** 压缩测试* 可以传文件或者目录*///      zip("M:\\ZIP\\test\\bb\\a\\t.txt", "M:\\ZIP\\test\\temp1.zip", "zyh");//      zip("M:\\ZIP\\test\\bb", "M:\\ZIP\\test\\temp2.zip", "zyh");        unzip("M:\\ZIP\\test\\temp2.zip", "M:\\ZIP\\test\\temp", "zyh");}}

压缩多个文件时,有两个方法(第一种没试):

(1) 预先把多个文件压缩成zip,然后调用enc.addAll(inZipFile, password);方法将多个zip文件加进来。

(2)针对需要压缩的文件循环调用enc.add(inFile, password);,每次都用相同的密码。

修改源码后的项目可到上面提到的博客去下载,或者参照博客自己修改,其实也很容易,毕竟只有几处改动。

另外我的CSDN下载频道也上传了修改后的源码和jar包,也可以去那里下载。

修改记录

需要修改的文件有:

  • ExtZipOutputStream
  • ExtZipEntry
  • AesZipFileEncrypter

在ExtZipOutputStream里增加一成员变量并添加两个方法:

protected String encoding = "iso-8859-1";   public boolean utf8Flg = false;public void setEncoding(String encoding) {this.encoding = encoding;utf8Flg |= isUTF8(encoding);}protected boolean isUTF8(String encoding) {if (encoding == null) {// check platform's default encodingencoding = System.getProperty("file.encoding");}return "UTF8".equalsIgnoreCase(encoding)|| "UTF-8".equalsIgnoreCase(encoding);}

然后将ExtZipOutputStream的(134行和158行左右)iso-8859-1编码替换成上面设置的编码格式 

接着,再将106行左右文件名长度取得代码改成:

1writeShort(entry.getName().getBytes(encoding).length); // file name length

这里有个地方需要注意,当文件名是utf8编码格式的时候,需要设置Zip包的通用位标志 (不明白)

第十一个比特为1,代码修改如下: 

修改ExtZipEntry类在initEncryptedEntry方法基础上增加一个重载方法:

public void initEncryptedEntry(boolean utf8Flag) {setCrc(0); // CRC-32 / for encrypted files it's 0 as AES/MAC checks integritiy this.flag |= 1; // bit0 - encryptedif (utf8Flag) {this.flag |=(1 << 11);}// flag |= 8; // bit3 - use data descriptorthis.primaryCompressionMethod = 0x63; byte[] extraBytes = new byte[11];extraBytes = new byte[11];// extra data header ID for AES encryption is 0x9901extraBytes[0] = 0x01;extraBytes[1] = (byte)0x99; // data size (currently 7, but subject to possible increase in the// future)extraBytes[2] = 0x07; // data sizeextraBytes[3] = 0x00; // data size// Integer version number specific to the zip vendorextraBytes[4] = 0x02; // version numberextraBytes[5] = 0x00; // version number // 2-character vendor IDextraBytes[6] = 0x41; // vendor idextraBytes[7] = 0x45; // vendor id // AES encryption strength - 1=128, 2=192, 3=256extraBytes[8] = 0x03; // actual compression method - 0x0000==stored (no compression) - 2 bytesextraBytes[9] = (byte) (getMethod() & 0xff);extraBytes[10] = (byte) ((getMethod() & 0xff00) >> 8); setExtra(extraBytes);}

其实就是增加一个参数并增加了下面这段代码:

123if (utf8Flag) {this.flag |=(1 << 11);}

当然不要忘了将调用该方法地方修改一下,传进utf8Flag参数

AesZipFileEncrypter类里有两处(在两个add方法中)其它地方不需改动。

https://www.jb51.net/article/164137.htm

51行代码实现简单的PHP区块链

今年区块链特别火,我也很火啊。我火什么呢。前几年,公众平台出现,还得花时间去学去看,后来小程序出现,又得花时间精力去学去看。现在比特币、以太坊等去中心化货币带起了区块链的发展。还得学。

没办法,技术改变师姐。不,是改变世界。

前些天看到python写的50行代码实现的简单区块链。今天让我们PHP也实现一下区块链的简单流程。

phper或其他人如有需要可加PHP区块链交流群(370648191/201923866)。

只有一个类、4个方法。可直接运行。

<?php/** * 简单的PHP区块链 * @author Yoper * @PHP技术交流QQ群 370648191 * @Email chen.yong.peng@foxmail.com * @wechat YoperMan */namespace common\library\block;/** * 区块结构 */class block{    private $index;    private $timestamp;    private $data;    private $previous_hash;    private $random_str;    private $hash;    public function __construct($index,$timestamp,$data,$random_str,$previous_hash)    {        $this->index=$index;        $this->timestamp=$timestamp;        $this->data=$data;        $this->previous_hash=$previous_hash;        $this->random_str=$random_str;        $this->hash=$this->hash_block();    }    public function __get($name){        return $this->$name;    }    private function hash_block(){        $str=$this->index.$this->timestamp.$this->data.$this->random_str.$this->previous_hash;        return hash("sha256",$str);    }}/** * 创世区块 * @return \common\library\block\block */function create_genesis_block(){    return new \common\library\block\block(0, time(),"第一个区块",0,0);}/** * 挖矿,生成下一个区块 * 这应该是一个复杂的算法,但为了简单,我们这里挖到前1位是数字就挖矿成功。 * @param \common\library\block\block $last_block_obj */function dig(\common\library\block\block $last_block_obj){    $random_str = $last_block_obj->hash.get_random();    $index=$last_block_obj->index+1;    $timestamp=time();    $data='I am block '.$index;    $block_obj = new \common\library\block\block($index,$timestamp,$data,$random_str,$last_block_obj->hash);        //前一位不是数字    if(!is_numeric($block_obj->hash{0})){        return false;    }    //数数字,返回块    return $block_obj;}/** * 验证区块 * 这也是一个复杂的过程,为了简单,我们这里直接返回正确 * @param array $data */function verify(\common\library\block\block $last_block_obj){    return true;}/** * 生成随机字符串 * @param int $len * @return string */function get_random($len=32){    $str="0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";    $key = "";    for($i=0;$i<$len;$i++)    {        $key.= $str{mt_rand(0,32)};//随机数    }    return $key;}  header("Content-type:text/html;charset=utf-8");//生成第一个区块$blockchain=[\common\library\block\create_genesis_block()];//模拟生成其他区块,我们直接循环生成。实际中,还需要跟踪互联网上多台机器上链的变化,像比特币会有工作量证明等算法,达到条件了才生成区块等//我们的链是一个数组,实际生产中应该保存下来$previous_block = $blockchain[0];for($i=0;$i<=10;$i++){    if(!($new_block=dig($previous_block))){        continue;    }    $blockchain[]=$new_block;    $previous_block=$new_block;        //告诉大家新增了一个区块    echo "区块已加入链中.新区块是 : {$new_block->index}<br/>";    echo "新区块哈希值是 : {$new_block->hash}<br/>";    print_r($new_block);    echo "<br/><br/>";}

以上文件可以直接运行。运行结果如下:

完善之后,就可以发行自己的货币或者智能合约了。

https://github.com/liexusong/blockchain-php/tree/v2.0

比特币挖矿挖矿算法

比特币的挖矿算法的流程。

区块头

这里写图片描述

首先挖矿算法的目标对象只是区块中的区块头,共80个字节,我们来看看区块头有哪些字段: 

注意:

  1. Target(难度目标):该区块工作量证明算法的难度目标
  2. 其实区块头不包括Padding+Length部分,这个部分只是为了满足SHA256算法的使用条件。
  3. 字段的不同颜色代表该字段内容变化频率。绿色:像Version、Target、以及Padding+Length(有约定俗成的标准)这些内容相对出块的速度来说变化频率很低;黄色:像hashPreBlock、hashMerkleRoot、Timestamp这些几乎与出块的速度一致的频率;红色:像Nonce这个的变化频率远远快与出块速率。

挖矿算法流程

为什么要做双重hash(SHA256(1)后还来个SHA256(2))

The SHA256 hashing algorithm, like all hashes constructed using the Merkle-Damgård paradigm, is vulnerable to this attack. The length extension attack allows an attacker who knows SHA256(x) to calculate SHA256(x||y) without the knowledge of x. Although it is unclear how length extension attacks may make the Bitcoin protocol susceptible to harm, it is believed that Satoshi Nakamoto decided to play it safe and include the double hashing in his design.

Another explanation [6] for this double hashing is that 128 rounds of SHA256 may remain safe longer if in the far future, a practical pre-image or a partial pre-image attack was found against SHA256.

挖矿算法实现以及编码方式

https://en.bitcoin.it/wiki/Block_hashing_algorithm

Bitcoin mining uses the hashcash proof of work function; the hashcash algorithm requires the following parameters: a service string, a nonce, and a counter. In bitcoin the service string is encoded in the block header data structure, and includes a version field, the hash of the previous block, the root hash of the merkle tree of all transactions in the block, the current time, and the difficulty. Bitcoin stores the nonce in the extraNonce field which is part of the coinbase transaction, which is stored as the left most leaf node in the merkle tree (the coinbase is the special first transaction in the block). The counter parameter is small at 32-bits so each time it wraps the extraNonce field must be incremented (or otherwise changed) to avoid repeating work. The basics of the hashcash algorithm are quite easy to understand and it is described in more detail here. When mining bitcoin, the hashcash algorithm repeatedly hashes the block header while incrementing the counter & extraNonce fields. Incrementing the extraNonce field entails recomputing the merkle tree, as the coinbase transaction is the left most leaf node. The block is also occasionally updated as you are working on it.

A block header contains these fields:

FieldPurposeUpdated when…Size (Bytes)
VersionBlock version numberYou upgrade the software and it specifies a new version4
hashPrevBlock256-bit hash of the previous block headerA new block comes in32
hashMerkleRoot256-bit hash based on all of the transactions in the blockA transaction is accepted32
TimeCurrent timestamp as seconds since 1970-01-01T00:00 UTCEvery few seconds4
BitsCurrent target in compact formatThe difficulty is adjusted4
Nonce32-bit number (starts at 0)A hash is tried (increments)4

The body of the block contains the transactions. These are hashed only indirectly through the Merkle root. Because transactions aren’t hashed directly, hashing a block with 1 transaction takes exactly the same amount of effort as hashing a block with 10,000 transactions.

The compact format of target is a special kind of floating-point encoding using 3 bytes mantissa, the leading byte as exponent (where only the 5 lowest bits are used) and its base is 256. Most of these fields will be the same for all users. There might be some minor variation in the timestamps. The nonce will usually be different, but it increases in a strictly linear way. “Nonce” starts at 0 and is incremented for each hash. Whenever Nonce overflows (which it does frequently), the extraNonce portion of the generation transaction is incremented, which changes the Merkle root.

Moreover, it is extremely unlikely for two people to have the same Merkle root because the first transaction in your block is a generation “sent” to one of your unique Bitcoin addresses. Since your block is different from everyone else’s blocks, you are (nearly) guaranteed to produce different hashes. Every hash you calculate has the same chance of winning as every other hash calculated by the network.

Bitcoin uses: SHA256(SHA256(Block_Header)) but you have to be careful about byte-order.

For example, this python code will calculate the hash of the block with the smallest hash as of June 2011, Block 125552. The header is built from the six fields described above, concatenated together as little-endian values in hex notation:

>>> import hashlib
>>> header_hex = ("01000000" +
 "81cd02ab7e569e8bcd9317e2fe99f2de44d49ab2b8851ba4a308000000000000" +
 "e320b6c2fffc8d750423db8b1eb942ae710e951ed797f7affc8892b0f1fc122b" +
 "c7f5d74d" +
 "f2b9441a" +
 "42a14695")
>>> header_bin = header_hex.decode('hex')
>>> hash = hashlib.sha256(hashlib.sha256(header_bin).digest()).digest()
>>> hash.encode('hex_codec')
'1dbd981fe6985776b644b173a4d0385ddc1aa2a829688d1e0000000000000000'
>>> hash[::-1].encode('hex_codec')
'00000000000000001e8d6829a8a21adc5d38d0a473b144b6765798e61f98bd1d'

Endianess

Note that the hash, which is a 256-bit number, has lots of leading zero bytes when stored or printed as a big-endian hexadecimal constant, but it has trailing zero bytes when stored or printed in little-endian. For example, if interpreted as a string and the lowest (or start of) the string address keeps lowest significant byte, it is little-endian.

The output of blockexplorer displays the hash values as big-endian numbers; notation for numbers is usual (leading digits are the most significant digits read from left to right).

For another example, here is a version in plain C without any optimization, threading or error checking.

Here is the same example in plain PHP without any optimization.

<?
  //This reverses and then swaps every other char
  function SwapOrder($in){
      $Split = str_split(strrev($in));
      $x='';
      for ($i = 0; $i < count($Split); $i+=2) {
          $x .= $Split[$i+1].$Split[$i];
      } 
      return $x;
  }
 
  //makes the littleEndian
  function littleEndian($value){
      return implode (unpack('H*',pack("V*",$value)));
  }
 
  $version = littleEndian(1);
  $prevBlockHash = SwapOrder('00000000000008a3a41b85b8b29ad444def299fee21793cd8b9e567eab02cd81');
  $rootHash = SwapOrder('2b12fcf1b09288fcaff797d71e950e71ae42b91e8bdb2304758dfcffc2b620e3');
  $time = littleEndian(1305998791);
  $bits = littleEndian(440711666); 
  $nonce = littleEndian(2504433986); 
 
  //concat it all
  $header_hex = $version . $prevBlockHash . $rootHash . $time . $bits . $nonce;
 
  //convert from hex to binary 
  $header_bin  = hex2bin($header_hex);
  //hash it then convert from hex to binary 
  $pass1 = hex2bin(  hash('sha256', $header_bin )  );
  //Hash it for the seconded time
  $pass2 = hash('sha256', $pass1);
  //fix the order
  $FinalHash = SwapOrder($pass2);
 
  echo   $FinalHash;
?>

Category

PHP 解决高并发

我们通常衡量一个Web系统的吞吐率的指标是QPS(Query Per Second,每秒处理请求数),解决每秒数万次的高并发场景,这个指标非常关键。举个例子,我们假设处理一个业务请求平均响应时间为100ms,同时,系统内有20台Apache的Web服务器,配置MaxClients为500个(表示Apache的最大连接数目)。

那么,我们的Web系统的理论峰值QPS为(理想化的计算方式):

20*500/0.1 = 100000 (10万QPS)

咦?我们的系统似乎很强大,1秒钟可以处理完10万的请求,5w/s的秒杀似乎是“纸老虎”哈。实际情况,当然没有这么理想。在高并发的实际场景下,机器都处于高负载的状态,在这个时候平均响应时间会被大大增加。

普通的一个p4的服务器每天最多能支持大约10万左右的IP,如果访问量超过10W那么需要专用的服务器才能解决,如果硬件不给力 软件怎么优化都是于事无补的。主要影响服务器的速度

有:网络-硬盘读写速度-内存大小-cpu处理速度。

就Web服务器而言,Apache打开了越多的连接进程,CPU需要处理的上下文切换也越多,额外增加了CPU的消耗,然后就直接导致平均响应时间增加。因此上述的MaxClient数目,要根据CPU、内存等硬件因素综合考虑,绝对不是越多越好。可以通过Apache自带的abench来测试一下,取一个合适的值。然后,我们选择内存操作级别的存储的Redis,在高并发的状态下,存储的响应时间至关重要。网络带宽虽然也是一个因素,不过,这种请求数据包一般比较小,一般很少成为请求的瓶颈。负载均衡成为系统瓶颈的情况比较少,在这里不做讨论哈。

那么问题来了,假设我们的系统,在5w/s的高并发状态下,平均响应时间从100ms变为250ms(实际情况,甚至更多):

20*500/0.25 = 40000 (4万QPS)

于是,我们的系统剩下了4w的QPS,面对5w每秒的请求,中间相差了1w。

举个例子,高速路口,1秒钟来5部车,每秒通过5部车,高速路口运作正常。突然,这个路口1秒钟只能通过4部车,车流量仍然依旧,结果必定出现大塞车。(5条车道忽然变成4条车道的感觉)

同理,某一个秒内,20*500个可用连接进程都在满负荷工作中,却仍然有1万个新来请求,没有连接进程可用,系统陷入到异常状态也是预期之内。

14834077821.jpg

其实在正常的非高并发的业务场景中,也有类似的情况出现,某个业务请求接口出现问题,响应时间极慢,将整个Web请求响应时间拉得很长,逐渐将Web服务器的可用连接数占满,其他正常的业务请求,无连接进程可用。

更可怕的问题是,是用户的行为特点,系统越是不可用,用户的点击越频繁,恶性循环最终导致“雪崩”(其中一台Web机器挂了,导致流量分散到其他正常工作的机器上,再导致正常的机器也挂,然后恶性循环),将整个Web系统拖垮。

3. 重启与过载保护

如果系统发生“雪崩”,贸然重启服务,是无法解决问题的。最常见的现象是,启动起来后,立刻挂掉。这个时候,最好在入口层将流量拒绝,然后再将重启。如果是redis/memcache这种服务也挂了,重启的时候需要注意“预热”,并且很可能需要比较长的时间。

秒杀和抢购的场景,流量往往是超乎我们系统的准备和想象的。这个时候,过载保护是必要的。如果检测到系统满负载状态,拒绝请求也是一种保护措施。在前端设置过滤是最简单的方式,但是,这种做法是被用户“千夫所指”的行为。更合适一点的是,将过载保护设置在CGI入口层,快速将客户的直接请求返回

高并发下的数据安全

我们知道在多线程写入同一个文件的时候,会存现“线程安全”的问题(多个线程同时运行同一段代码,如果每次运行结果和单线程运行的结果是一样的,结果和预期相同,就是线程安全的)。如果是MySQL数据库,可以使用它自带的锁机制很好的解决问题,但是,在大规模并发的场景中,是不推荐使用MySQL的。秒杀和抢购的场景中,还有另外一个问题,就是“超发”,如果在这方面控制不慎,会产生发送过多的情况。我们也曾经听说过,某些电商搞抢购活动,买家成功拍下后,商家却不承认订单有效,拒绝发货。这里的问题,也许并不一定是商家奸诈,而是系统技术层面存在超发风险导致的。

1. 超发的原因

假设某个抢购场景中,我们一共只有100个商品,在最后一刻,我们已经消耗了99个商品,仅剩最后一个。这个时候,系统发来多个并发请求,这批请求读取到的商品余量都是99个,然后都通过了这一个余量判断,最终导致超发。(同文章前面说的场景)

14834077822.jpg

在上面的这个图中,就导致了并发用户B也“抢购成功”,多让一个人获得了商品。这种场景,在高并发的情况下非常容易出现。

优化方案1:将库存字段number字段设为unsigned,当库存为0时,因为字段不能为负数,将会返回false

12345678910111213141516171819202122232425262728293031323334353637383940414243444546<?php//优化方案1:将库存字段number字段设为unsigned,当库存为0时,因为字段不能为负数,将会返回falseinclude('./mysql.php');$username 'wang'.rand(0,1000);//生成唯一订单function build_order_no(){return date('ymd').substr(implode(NULL, array_map('ord'str_split(substr(uniqid(), 7, 13), 1))), 0, 8);}//记录日志function insertLog($event,$type=0,$username){global $conn;$sql="insert into ih_log(event,type,usernma)values('$event','$type','$username')";return mysqli_query($conn,$sql);}function insertOrder($order_sn,$user_id,$goods_id,$sku_id,$price,$username,$number){global $conn;$sql="insert into ih_order(order_sn,user_id,goods_id,sku_id,price,username,number)values('$order_sn','$user_id','$goods_id','$sku_id','$price','$username','$number')";return  mysqli_query($conn,$sql);}//模拟下单操作//库存是否大于0$sql="select number from ih_store where goods_id='$goods_id' and sku_id='$sku_id' ";$rs=mysqli_query($conn,$sql);$row $rs->fetch_assoc();if($row['number']>0){//高并发下会导致超卖if($row['number']<$number){return insertLog('库存不够',3,$username);}$order_sn=build_order_no();//库存减少$sql="update ih_store set number=number-{$number} where sku_id='$sku_id' and number>0";$store_rs=mysqli_query($conn,$sql);if($store_rs){//生成订单insertOrder($order_sn,$user_id,$goods_id,$sku_id,$price,$username,$number);insertLog('库存减少成功',1,$username);}else{insertLog('库存减少失败',2,$username);}}else{insertLog('库存不够',3,$username);}?>

2. 悲观锁思路

解决线程安全的思路很多,可以从“悲观锁”的方向开始讨论。

悲观锁,也就是在修改数据的时候,采用锁定状态,排斥外部请求的修改。遇到加锁的状态,就必须等待。

14834077833.jpg

虽然上述的方案的确解决了线程安全的问题,但是,别忘记,我们的场景是“高并发”。也就是说,会很多这样的修改请求,每个请求都需要等待“锁”,某些线程可能永远都没有机会抢到这个“锁”,这种请求就会死在那里。同时,这种请求会很多,瞬间增大系统的平均响应时间,结果是可用连接数被耗尽,系统陷入异常。

优化方案2:使用MySQL的事务,锁住操作的行

12345678910111213141516171819202122232425262728293031323334353637383940414243<?php//优化方案2:使用MySQL的事务,锁住操作的行include('./mysql.php');//生成唯一订单号function build_order_no(){return date('ymd').substr(implode(NULL, array_map('ord'str_split(substr(uniqid(), 7, 13), 1))), 0, 8);}//记录日志function insertLog($event,$type=0){global $conn;$sql="insert into ih_log(event,type)values('$event','$type')";mysqli_query($conn,$sql);}//模拟下单操作//库存是否大于0mysqli_query($conn,"BEGIN");  //开始事务$sql="select number from ih_store where goods_id='$goods_id' and sku_id='$sku_id' FOR UPDATE";//此时这条记录被锁住,其它事务必须等待此次事务提交后才能执行$rs=mysqli_query($conn,$sql);$row=$rs->fetch_assoc();if($row['number']>0){//生成订单$order_sn=build_order_no();$sql="insert into ih_order(order_sn,user_id,goods_id,sku_id,price)values('$order_sn','$user_id','$goods_id','$sku_id','$price')";$order_rs=mysqli_query($conn,$sql);//库存减少$sql="update ih_store set number=number-{$number} where sku_id='$sku_id'";$store_rs=mysqli_query($conn,$sql);if($store_rs){echo '库存减少成功';insertLog('库存减少成功');mysqli_query($conn,"COMMIT");//事务提交即解锁}else{echo '库存减少失败';insertLog('库存减少失败');}}else{echo '库存不够';insertLog('库存不够');mysqli_query($conn,"ROLLBACK");}?>

3. FIFO队列思路

那好,那么我们稍微修改一下上面的场景,我们直接将请求放入队列中的,采用FIFO(First Input First Output,先进先出),这样的话,我们就不会导致某些请求永远获取不到锁。看到这里,是不是有点强行将多线程变成单线程的感觉哈。

14834077834.jpg

然后,我们现在解决了锁的问题,全部请求采用“先进先出”的队列方式来处理。那么新的问题来了,高并发的场景下,因为请求很多,很可能一瞬间将队列内存“撑爆”,然后系统又陷入到了异常状态。或者设计一个极大的内存队列,也是一种方案,但是,系统处理完一个队列内请求的速度根本无法和疯狂涌入队列中的数目相比。也就是说,队列内的请求会越积累越多,最终Web系统平均响应时候还是会大幅下降,系统还是陷入异常。

4. 文件锁的思路

对于日IP不高或者说并发数不是很大的应用,一般不用考虑这些!用一般的文件操作方法完全没有问题。但如果并发高,在我们对文件进行读写操作时,很有可能多个进程对进一文件进行操作,如果这时不对文件的访问进行相应的独占,就容易造成数据丢失

优化方案4:使用非阻塞的文件排他锁

12345678910111213141516171819202122232425262728293031323334353637383940414243444546<?php//优化方案4:使用非阻塞的文件排他锁include ('./mysql.php');//生成唯一订单号function build_order_no(){return date('ymd').substr(implode(NULL, array_map('ord'str_split(substr(uniqid(), 7, 13), 1))), 0, 8);}//记录日志function insertLog($event,$type=0){global $conn;$sql="insert into ih_log(event,type)values('$event','$type')";mysqli_query($conn,$sql);}$fp fopen("lock.txt""w+");if(!flock($fp,LOCK_EX | LOCK_NB)){echo "系统繁忙,请稍后再试";return;}//下单$sql="select number from ih_store where goods_id='$goods_id' and sku_id='$sku_id'";$rs =  mysqli_query($conn,$sql);$row $rs->fetch_assoc();if($row['number']>0){//库存是否大于0//模拟下单操作$order_sn=build_order_no();$sql="insert into ih_order(order_sn,user_id,goods_id,sku_id,price)values('$order_sn','$user_id','$goods_id','$sku_id','$price')";$order_rs =  mysqli_query($conn,$sql);//库存减少$sql="update ih_store set number=number-{$number} where sku_id='$sku_id'";$store_rs =  mysqli_query($conn,$sql);if($store_rs){echo '库存减少成功';insertLog('库存减少成功');flock($fp,LOCK_UN);//释放锁}else{echo '库存减少失败';insertLog('库存减少失败');}}else{echo '库存不够';insertLog('库存不够');}fclose($fp);?>

5. 乐观锁思路

这个时候,我们就可以讨论一下“乐观锁”的思路了。乐观锁,是相对于“悲观锁”采用更为宽松的加锁机制,大都是采用带版本号(Version)更新。实现就是,这个数据所有请求都有资格去修改,但会获得一个该数据的版本号,只有版本号符合的才能更新成功,其他的返回抢购失败。这样的话,我们就不需要考虑队列的问题,不过,它会增大CPU的计算开销。但是,综合来说,这是一个比较好的解决方案。

14834077835.jpg

有很多软件和服务都“乐观锁”功能的支持,例如Redis中的watch就是其中之一。通过这个实现,我们保证了数据的安全。

优化方案5:Redis中的watch

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253<?php$redis new redis();$result $redis->connect('127.0.0.1', 6379);echo $mywatchkey $redis->get("mywatchkey");/*//插入抢购数据if($mywatchkey>0){$redis->watch("mywatchkey");//启动一个新的事务。$redis->multi();$redis->set("mywatchkey",$mywatchkey-1);$result = $redis->exec();if($result) {$redis->hSet("watchkeylist","user_".mt_rand(1,99999),time());$watchkeylist = $redis->hGetAll("watchkeylist");echo "抢购成功!<br/>";$re = $mywatchkey - 1;  echo "剩余数量:".$re."<br/>";echo "用户列表:<pre>";print_r($watchkeylist);}else{echo "手气不好,再抢购!";exit;}else{// $redis->hSet("watchkeylist","user_".mt_rand(1,99999),"12");//  $watchkeylist = $redis->hGetAll("watchkeylist");echo "fail!<br/>";   echo ".no result<br/>";echo "用户列表:<pre>";//  var_dump($watchkeylist); }*/$rob_total = 100;   //抢购数量if($mywatchkey<=$rob_total){$redis->watch("mywatchkey");$redis->multi(); //在当前连接上启动一个新的事务。//插入抢购数据$redis->set("mywatchkey",$mywatchkey+1);$rob_result $redis->exec();if($rob_result){$redis->hSet("watchkeylist","user_".mt_rand(1, 9999),$mywatchkey);$mywatchlist $redis->hGetAll("watchkeylist");echo "抢购成功!<br/>"; echo "剩余数量:".($rob_total-$mywatchkey-1)."<br/>";echo "用户列表:<pre>";var_dump($mywatchlist);}else{$redis->hSet("watchkeylist","user_".mt_rand(1, 9999),'meiqiangdao');echo "手气不好,再抢购!";exit;}}?>

PHP解决网站大数据大流量与高并发

第一个要说的就是数据库,首先要有一个很好的架构,查询尽量不用* 避免相关子查询 给经常查询的添加索引 用排序来取代非顺序存取,如果条件允许 ,一般MySQL服务器最好安装在Linux操作系统中 。关于apache和nginx在高并发的情况下推荐使用nginx,ginx是Apache服务器不错的替代品。nginx内存消耗少 官方测试能够支撑5万并发连接,在实际生产环境中跑到2~3万并发连接数。php方面不需要的模块尽量关闭,使用memcached,Memcached 是一个高性能的分布式内存对象缓存系统,不使用数据库直接从内存当中调数据,这样大大提升了速度,iiS或Apache启用GZIP压缩优化网站,压缩网站内容大大节省网站流量。

第二,禁止外部的盗链。

外部网站的图片或者文件盗链往往会带来大量的负载压力,因此应该严格限制外部对
于自身的图片或者文件盗链,好在目前可以简单地通过refer来控制盗链,Apache自
己就可以通过配置来禁止盗链,IIS也有一些第三方的ISAPI可以实现同样的功能。当
然,伪造refer也可以通过代码来实现盗链,不过目前蓄意伪造refer盗链的还不多,
可以先不去考虑,或者使用非技术手段来解决,比如在图片上增加水印。

第三,控制大文件的下载。

大文件的下载会占用很大的流量,并且对于非SCSI硬盘来说,大量文件下载会消耗
CPU,使得网站响应能力下降。因此,尽量不要提供超过2M的大文件下载,如果需要
提供,建议将大文件放在另外一台服务器上。

第四,使用不同主机分流主要流量

将文件放在不同的主机上,提供不同的镜像供用户下载。比如如果觉得RSS文件占用
流量大,那么使用FeedBurner或者FeedSky等服务将RSS输出放在其他主机上,这
样别人访问的流量压力就大多集中在FeedBurner的主机上,RSS就不占用太多资源了

第五,使用不同主机分流主要流量
将文件放在不同的主机上,提供不同的镜像供用户下载。比如如果觉得RSS文件占用流量大,那么使用FeedBurner或者FeedSky等服务将RSS输出放在其他主机上,这样别人访问的流量压力就大多集中在FeedBurner的主机上,RSS就不占用太多资源了。

第六,使用流量分析统计软件。
在网站上安装一个流量分析统计软件,可以即时知道哪些地方耗费了大量流量,哪些页面需要再进行优化,因此,解决流量问题还需要进行精确的统计分析才可以。比如:Google Analytics(Google分析)。

高并发和高负载的约束条件:硬件、部署、操作系统、Web 服务器、PHP、MySQL、测试

部署:服务器分离、数据库集群和库表散列、镜像、负载均衡

负载均衡分类: 1)、DNS轮循 2)代理服务器负载均衡 3)地址转换网关负载均衡 4)NAT负载均衡 5)反向代理负载均衡 6)混合型负载均衡

部署方案1:

适用范围:静态内容为主体的网站和应用系统;对系统安全要求较高的网站和应用系统。

Main Server:主服务器

承载程序的主体运行压力,处理网站或应用系统中的动态请求;

将静态页面推送至多个发布服务器;

将附件文件推送至文件服务器;

安全要求较高,以静态为主的网站,可将服务器置于内网屏蔽外网的访问。

DB Server:数据库服务器

承载数据库读写压力;

只与主服务器进行数据量交换,屏蔽外网访问。

File/Video Server:文件/视频服务器

承载系统中占用系统资源和带宽资源较大的数据流;

作为大附件的存储和读写仓库;

作为视频服务器将具备视频自动处理能力。

发布服务器组:

只负责静态页面的发布,承载绝大多数的Web请求;

通过Nginx进行负载均衡部署。

部署方案2:

适用范围:以动态交互内容为主体的网站或应用系统;负载压力较大,且预算比较充足的网站或应用系统;

Web服务器组:

Web服务无主从关系,属平行冗余设计;

通过前端负载均衡设备或Nginx反向代理实现负载均衡;

划分专用文件服务器/视频服务器有效分离轻/重总线;

每台Web服务器可通过DEC可实现连接所有数据库,同时划分主从。

数据库服务器组:

相对均衡的承载数据库读写压力;

通过数据库物理文件的映射实现多数据库的数据同步。

共享磁盘/磁盘阵列

将用于数据物理文件的统一读写

用于大型附件的存储仓库

通过自身物理磁盘的均衡和冗余,确保整体系统的IO效率和数据安全;

方案特性:

通过前端负载均衡,合理分配Web压力;

通过文件/视频服务器与常规Web服务器的分离,合理分配轻重数据流;

通过数据库服务器组,合理分配数据库IO压力;

每台Web服务器通常只连接一台数据库服务器,通过DEC的心跳检测,可在极短时间内自动切换至冗余数据库服务器;

磁盘阵列的引入,大幅提升系统IO效率的同时,极大增强了数据安全性。

Web服务器:

Web服务器很大一部分资源占用来自于处理Web请求,通常情况下这也就是Apache产生的压力,在高并发连接的情况下,Nginx是Apache服务器不错的替代品。Nginx (“engine x”) 是俄罗斯人编写的一款高性能的 HTTP 和反向代理服务器。在国内,已经有新浪、搜狐通行证、网易新闻、网易博客、金山逍遥网、金山爱词霸、校内网、YUPOO相册、豆瓣、迅雷看看等多家网站、 频道使用 Nginx 服务器。

Nginx的优势:

高并发连接:官方测试能够支撑5万并发连接,在实际生产环境中跑到2~3万并发连接数。

内存消耗少:在3万并发连接下,开启的10个Nginx 进程才消耗150M内存(15M*10=150M)。

内置的健康检查功能:如果 Nginx Proxy 后端的某台 Web 服务器宕机了,不会影响前端访问。

策略:相对于老牌的Apache,我们选择Lighttpd和Nginx这些具有更小的资源占用率和更高的负载能力的web服务器。

Mysql:

MySQL本身具备了很强的负载能力,MySQL优化是一项很复杂的工作,因为这最终需要对系统优化的很好理解。大家都知道数据库工作就是大量的、 短时的查询和读写,除了程序开发时需要注意建立索引、提高查询效率等软件开发技巧之外,从硬件设施的角度影响MySQL执行效率最主要来自于磁盘搜索、磁盘IO水平、CPU周期、内存带宽。

  根据服务器上的硬件和软件条件进行MySQl优化。MySQL优化的核心在于系统资源的分配,这不等于无限制的给MySQL分配更多的资源。在MySQL配置文件中我们介绍几个最值得关注的参数:

改变索引缓冲区长度(key_buffer)

改变表长(read_buffer_size)

设定打开表的数目的最大值(table_cache)

对缓长查询设定一个时间限制(long_query_time)

如果条件允许 ,一般MySQL服务器最好安装在Linux操作系统中,而不是安装在FreeBSD中。
策略: MySQL优化需要根据业务系统的数据库读写特性和服务器硬件配置,制定不同的优化方案,并且可以根据需要部署MySQL的主从结构。

PHP:

1、加载尽可能少的模块;

2、如果是在windows平台下,尽可能使用IIS或者Nginx来替代我们平常用的Apache;

3、安装加速器(都是通过缓存php代码预编译的结果和数据库结果来提高php代码的执行速度)
eAccelerator,eAccelerator是一个自由开放源码php加速器,优化和动态内容缓存,提高了性能php脚本的缓存性能,使得PHP脚本在编译的状态下,对服务器的开销几乎完全消除。

Apc:Alternative PHP Cache(APC)是 PHP 的一个免费公开的优化代码缓存。它用来提供免费,公开并且强健的架构来缓存和优化 PHP 的中间代码。

memcache:memcache是由Danga Interactive开发的,高性能的,分布式的内存对象缓存系统,用于在动态应用中减少数据库负载,提升访问速度。主要机制是通过在内存里维护一个统 一的巨大的hash表,Memcache能够用来存储各种格式的数据,包括图像、视频、文件以及数据库检索的结果等

Xcache:国人开发的缓存器,

策略: 为PHP安装加速器。

代理服务器(缓存服务器):

Squid Cache(简称为Squid)是一个流行的自由软件(GNU通用公共许可证)的代理服务器和Web缓存服务器。Squid有广泛的用途,从作为网页服务器的前置cache服务器缓存相关请求来提高Web服务器的速度,到为一组人共享网络资源而缓存万维网,域名系统和其他网络搜索,到通过过滤流量帮助网络安全,到局域网通过代理网。Squid主要设计用于在Unix一类系统运行。

策略:安装Squid 反向代理服务器,能够大幅度提高服务器效率。

压力测试:压力测试是一种基本的质量保证行为,它是每个重要软件测试工作的一部分。压力测试的基本思路很简单:不是在常规条件下运行手动或自动测试,而是在计算机数量较少或系统资源匮乏的条件下运行测试。通常要进行压力测试的资源包括内部内存、CPU 可用性、磁盘空间和网络带宽等。一般用并发来做压力测试。
压力测试工具:webbench,ApacheBench等

漏洞测试:在我们的系统中漏洞主要包括:sql注入漏洞,xss跨站脚本攻击等。安全方面还包括系统软件,如操作系统漏洞,mysql、apache等的漏洞,一般可以通过升级来解决。

漏洞测试工具:Acunetix Web Vulnerability Scanner

ThinkPHP5中使用pthreads多线程

​​​做了个爬虫,因为PHP是单线程,所以爬取速度较慢,故使用了pthreads多线程,实现多线程爬取

pthreads扩展下载地址:http://windows.php.net/downloads/pecl/releases/pthreads

扩展文档:http://docs.php.net/manual/zh/book.pthreads.php

多线程代码

namespace app\api\controller\v1;
use think\Db; //此处的Db类都以失效,试了多种引入方式都不行
use think\Cache; //同理
use think\Controller;
class  Curl  extends \Thread
{
     public $url;
    public $result;
    public function __construct($url) {
        $this->url = $url;
    }
   //线程运行
    public function run() {
        if ($this->url) {
            $this->result = $this->doshu($this->url);

        }
    }
    public function doshu($url){
        return file_get_contents($url); //所需要访问的网址
    }
}

遇到的问题,线程中使用不了数据库,不知道什么原因,知道的大神可以解释一下,我用了访问内部的url来实现数据写入

public function doZhiHu1(){

//多个本地网址
        $urls = array(‘http://localhost/shopapi/api/v1.index/doZhiHu2’,
            ‘http://localhost/shopapi/api/v1.index/doZhiHu3’,
            ‘http://localhost/shopapi/api/v1.index/doZhiHu4’,
            ‘http://localhost/shopapi/api/v1.index/doZhiHu5’,
            ‘http://localhost/shopapi/api/v1.index/doZhiHu6’);
        foreach ($urls as $key=>$url) {
            $workers[$key] = new curl($url); //new一个新的线程
            $workers[$key]->start(); //开始运行
        }
        foreach ($workers as $key=>$worker) {
            while($workers[$key]->isRunning()) { //查看线程的状态
                usleep(100);  
            }
            if ($workers[$key]->join()) { //等待线程执行结束
                var_dump($workers[$key]->result);
            }
        }
    }

php如何处理大数据高并发

转发来自:https://www.cnblogs.com/walblog/articles/8476579.html(咸鱼想翻身

我们通常衡量一个Web系统的吞吐率的指标是QPS(Query Per Second,每秒处理请求数),解决每秒数万次的高并发场景,这个指标非常关键。举个例子,我们假设处理一个业务请求平均响应时间为100ms,同时,系统内有20台Apache的Web服务器,配置MaxClients为500个(表示Apache的最大连接数目)。

那么,我们的Web系统的理论峰值QPS为(理想化的计算方式):

20*500/0.1 = 100000 (10万QPS)

咦?我们的系统似乎很强大,1秒钟可以处理完10万的请求,5w/s的秒杀似乎是“纸老虎”哈。实际情况,当然没有这么理想。在高并发的实际场景下,机器都处于高负载的状态,在这个时候平均响应时间会被大大增加。

普通的一个p4的服务器每天最多能支持大约10万左右的IP,如果访问量超过10W那么需要专用的服务器才能解决,如果硬件不给力 软件怎么优化都是于事无补的。主要影响服务器的速度

有:网络-硬盘读写速度-内存大小-cpu处理速度。

就Web服务器而言,Apache打开了越多的连接进程,CPU需要处理的上下文切换也越多,额外增加了CPU的消耗,然后就直接导致平均响应时间增加。因此上述的MaxClient数目,要根据CPU、内存等硬件因素综合考虑,绝对不是越多越好。可以通过Apache自带的abench来测试一下,取一个合适的值。然后,我们选择内存操作级别的存储的Redis,在高并发的状态下,存储的响应时间至关重要。网络带宽虽然也是一个因素,不过,这种请求数据包一般比较小,一般很少成为请求的瓶颈。负载均衡成为系统瓶颈的情况比较少,在这里不做讨论哈。

那么问题来了,假设我们的系统,在5w/s的高并发状态下,平均响应时间从100ms变为250ms(实际情况,甚至更多):

20*500/0.25 = 40000 (4万QPS)

于是,我们的系统剩下了4w的QPS,面对5w每秒的请求,中间相差了1w。

举个例子,高速路口,1秒钟来5部车,每秒通过5部车,高速路口运作正常。突然,这个路口1秒钟只能通过4部车,车流量仍然依旧,结果必定出现大塞车。(5条车道忽然变成4条车道的感觉)

同理,某一个秒内,20*500个可用连接进程都在满负荷工作中,却仍然有1万个新来请求,没有连接进程可用,系统陷入到异常状态也是预期之内。

14834077821.jpg

其实在正常的非高并发的业务场景中,也有类似的情况出现,某个业务请求接口出现问题,响应时间极慢,将整个Web请求响应时间拉得很长,逐渐将Web服务器的可用连接数占满,其他正常的业务请求,无连接进程可用。

更可怕的问题是,是用户的行为特点,系统越是不可用,用户的点击越频繁,恶性循环最终导致“雪崩”(其中一台Web机器挂了,导致流量分散到其他正常工作的机器上,再导致正常的机器也挂,然后恶性循环),将整个Web系统拖垮。

3. 重启与过载保护

如果系统发生“雪崩”,贸然重启服务,是无法解决问题的。最常见的现象是,启动起来后,立刻挂掉。这个时候,最好在入口层将流量拒绝,然后再将重启。如果是redis/memcache这种服务也挂了,重启的时候需要注意“预热”,并且很可能需要比较长的时间。

秒杀和抢购的场景,流量往往是超乎我们系统的准备和想象的。这个时候,过载保护是必要的。如果检测到系统满负载状态,拒绝请求也是一种保护措施。在前端设置过滤是最简单的方式,但是,这种做法是被用户“千夫所指”的行为。更合适一点的是,将过载保护设置在CGI入口层,快速将客户的直接请求返回

高并发下的数据安全

我们知道在多线程写入同一个文件的时候,会存现“线程安全”的问题(多个线程同时运行同一段代码,如果每次运行结果和单线程运行的结果是一样的,结果和预期相同,就是线程安全的)。如果是MySQL数据库,可以使用它自带的锁机制很好的解决问题,但是,在大规模并发的场景中,是不推荐使用MySQL的。秒杀和抢购的场景中,还有另外一个问题,就是“超发”,如果在这方面控制不慎,会产生发送过多的情况。我们也曾经听说过,某些电商搞抢购活动,买家成功拍下后,商家却不承认订单有效,拒绝发货。这里的问题,也许并不一定是商家奸诈,而是系统技术层面存在超发风险导致的。

1. 超发的原因

假设某个抢购场景中,我们一共只有100个商品,在最后一刻,我们已经消耗了99个商品,仅剩最后一个。这个时候,系统发来多个并发请求,这批请求读取到的商品余量都是99个,然后都通过了这一个余量判断,最终导致超发。(同文章前面说的场景)

14834077822.jpg

在上面的这个图中,就导致了并发用户B也“抢购成功”,多让一个人获得了商品。这种场景,在高并发的情况下非常容易出现。

优化方案1:将库存字段number字段设为unsigned,当库存为0时,因为字段不能为负数,将会返回false

复制代码
复制代码
 1 <?php
 2 //优化方案1:将库存字段number字段设为unsigned,当库存为0时,因为字段不能为负数,将会返回false
 3 include('./mysql.php');
 4 $username = 'wang'.rand(0,1000);
 5 //生成唯一订单
 6 function build_order_no(){
 7   return date('ymd').substr(implode(NULL, array_map('ord', str_split(substr(uniqid(), 7, 13), 1))), 0, 8);
 8 }
 9 //记录日志
10 function insertLog($event,$type=0,$username){
11     global $conn;
12     $sql="insert into ih_log(event,type,usernma)
13     values('$event','$type','$username')";
14     return mysqli_query($conn,$sql);
15 }
16 function insertOrder($order_sn,$user_id,$goods_id,$sku_id,$price,$username,$number)
17 {
18       global $conn;
19       $sql="insert into ih_order(order_sn,user_id,goods_id,sku_id,price,username,number)
20       values('$order_sn','$user_id','$goods_id','$sku_id','$price','$username','$number')";
21      return  mysqli_query($conn,$sql);
22 }
23 //模拟下单操作
24 //库存是否大于0
25 $sql="select number from ih_store where goods_id='$goods_id' and sku_id='$sku_id' ";
26 $rs=mysqli_query($conn,$sql);
27 $row = $rs->fetch_assoc();
28   if($row['number']>0){//高并发下会导致超卖
29       if($row['number']<$number){
30         return insertLog('库存不够',3,$username);
31       }
32       $order_sn=build_order_no();
33       //库存减少
34       $sql="update ih_store set number=number-{$number} where sku_id='$sku_id' and number>0";
35       $store_rs=mysqli_query($conn,$sql);
36       if($store_rs){
37           //生成订单
38           insertOrder($order_sn,$user_id,$goods_id,$sku_id,$price,$username,$number);
39           insertLog('库存减少成功',1,$username);
40       }else{
41           insertLog('库存减少失败',2,$username);
42       }
43   }else{
44       insertLog('库存不够',3,$username);
45   }
46 ?>
复制代码
复制代码

2. 悲观锁思路

解决线程安全的思路很多,可以从“悲观锁”的方向开始讨论。

悲观锁,也就是在修改数据的时候,采用锁定状态,排斥外部请求的修改。遇到加锁的状态,就必须等待。

14834077833.jpg

虽然上述的方案的确解决了线程安全的问题,但是,别忘记,我们的场景是“高并发”。也就是说,会很多这样的修改请求,每个请求都需要等待“锁”,某些线程可能永远都没有机会抢到这个“锁”,这种请求就会死在那里。同时,这种请求会很多,瞬间增大系统的平均响应时间,结果是可用连接数被耗尽,系统陷入异常。

优化方案2:使用MySQL的事务,锁住操作的行

复制代码
复制代码
 1 <?php
 2 //优化方案2:使用MySQL的事务,锁住操作的行
 3 include('./mysql.php');
 4 //生成唯一订单号
 5 function build_order_no(){
 6   return date('ymd').substr(implode(NULL, array_map('ord', str_split(substr(uniqid(), 7, 13), 1))), 0, 8);
 7 }
 8 //记录日志
 9 function insertLog($event,$type=0){
10     global $conn;
11     $sql="insert into ih_log(event,type)
12     values('$event','$type')";
13     mysqli_query($conn,$sql);
14 }
15 //模拟下单操作
16 //库存是否大于0
17 mysqli_query($conn,"BEGIN");  //开始事务
18 $sql="select number from ih_store where goods_id='$goods_id' and sku_id='$sku_id' FOR UPDATE";//此时这条记录被锁住,其它事务必须等待此次事务提交后才能执行
19 $rs=mysqli_query($conn,$sql);
20 $row=$rs->fetch_assoc();
21 if($row['number']>0){
22     //生成订单
23     $order_sn=build_order_no();
24     $sql="insert into ih_order(order_sn,user_id,goods_id,sku_id,price)
25     values('$order_sn','$user_id','$goods_id','$sku_id','$price')";
26     $order_rs=mysqli_query($conn,$sql);
27     //库存减少
28     $sql="update ih_store set number=number-{$number} where sku_id='$sku_id'";
29     $store_rs=mysqli_query($conn,$sql);
30     if($store_rs){
31       echo '库存减少成功';
32         insertLog('库存减少成功');
33         mysqli_query($conn,"COMMIT");//事务提交即解锁
34     }else{
35       echo '库存减少失败';
36         insertLog('库存减少失败');
37     }
38 }else{
39   echo '库存不够';
40     insertLog('库存不够');
41     mysqli_query($conn,"ROLLBACK");
42 }
43 ?>
复制代码
复制代码

3. FIFO队列思路

那好,那么我们稍微修改一下上面的场景,我们直接将请求放入队列中的,采用FIFO(First Input First Output,先进先出),这样的话,我们就不会导致某些请求永远获取不到锁。看到这里,是不是有点强行将多线程变成单线程的感觉哈。

14834077834.jpg

然后,我们现在解决了锁的问题,全部请求采用“先进先出”的队列方式来处理。那么新的问题来了,高并发的场景下,因为请求很多,很可能一瞬间将队列内存“撑爆”,然后系统又陷入到了异常状态。或者设计一个极大的内存队列,也是一种方案,但是,系统处理完一个队列内请求的速度根本无法和疯狂涌入队列中的数目相比。也就是说,队列内的请求会越积累越多,最终Web系统平均响应时候还是会大幅下降,系统还是陷入异常。

4. 文件锁的思路

对于日IP不高或者说并发数不是很大的应用,一般不用考虑这些!用一般的文件操作方法完全没有问题。但如果并发高,在我们对文件进行读写操作时,很有可能多个进程对进一文件进行操作,如果这时不对文件的访问进行相应的独占,就容易造成数据丢失

优化方案4:使用非阻塞的文件排他锁

复制代码
复制代码
 1 <?php
 2 //优化方案4:使用非阻塞的文件排他锁
 3 include ('./mysql.php');
 4 //生成唯一订单号
 5 function build_order_no(){
 6   return date('ymd').substr(implode(NULL, array_map('ord', str_split(substr(uniqid(), 7, 13), 1))), 0, 8);
 7 }
 8 //记录日志
 9 function insertLog($event,$type=0){
10     global $conn;
11     $sql="insert into ih_log(event,type)
12     values('$event','$type')";
13     mysqli_query($conn,$sql);
14 }
15 $fp = fopen("lock.txt", "w+");
16 if(!flock($fp,LOCK_EX | LOCK_NB)){
17     echo "系统繁忙,请稍后再试";
18     return;
19 }
20 //下单
21 $sql="select number from ih_store where goods_id='$goods_id' and sku_id='$sku_id'";
22 $rs =  mysqli_query($conn,$sql);
23 $row = $rs->fetch_assoc();
24 if($row['number']>0){//库存是否大于0
25     //模拟下单操作
26     $order_sn=build_order_no();
27     $sql="insert into ih_order(order_sn,user_id,goods_id,sku_id,price)
28     values('$order_sn','$user_id','$goods_id','$sku_id','$price')";
29     $order_rs =  mysqli_query($conn,$sql);
30     //库存减少
31     $sql="update ih_store set number=number-{$number} where sku_id='$sku_id'";
32     $store_rs =  mysqli_query($conn,$sql);
33     if($store_rs){
34       echo '库存减少成功';
35         insertLog('库存减少成功');
36         flock($fp,LOCK_UN);//释放锁
37     }else{
38       echo '库存减少失败';
39         insertLog('库存减少失败');
40     }
41 }else{
42   echo '库存不够';
43     insertLog('库存不够');
44 }
45 fclose($fp);
46  ?>
复制代码
复制代码
复制代码
复制代码
 1 <?php
 2 //优化方案4:使用非阻塞的文件排他锁
 3 include ('./mysql.php');
 4 //生成唯一订单号
 5 function build_order_no(){
 6   return date('ymd').substr(implode(NULL, array_map('ord', str_split(substr(uniqid(), 7, 13), 1))), 0, 8);
 7 }
 8 //记录日志
 9 function insertLog($event,$type=0){
10     global $conn;
11     $sql="insert into ih_log(event,type)
12     values('$event','$type')";
13     mysqli_query($conn,$sql);
14 }
15 $fp = fopen("lock.txt", "w+");
16 if(!flock($fp,LOCK_EX | LOCK_NB)){
17     echo "系统繁忙,请稍后再试";
18     return;
19 }
20 //下单
21 $sql="select number from ih_store where goods_id='$goods_id' and sku_id='$sku_id'";
22 $rs =  mysqli_query($conn,$sql);
23 $row = $rs->fetch_assoc();
24 if($row['number']>0){//库存是否大于0
25     //模拟下单操作
26     $order_sn=build_order_no();
27     $sql="insert into ih_order(order_sn,user_id,goods_id,sku_id,price)
28     values('$order_sn','$user_id','$goods_id','$sku_id','$price')";
29     $order_rs =  mysqli_query($conn,$sql);
30     //库存减少
31     $sql="update ih_store set number=number-{$number} where sku_id='$sku_id'";
32     $store_rs =  mysqli_query($conn,$sql);
33     if($store_rs){
34       echo '库存减少成功';
35         insertLog('库存减少成功');
36         flock($fp,LOCK_UN);//释放锁
37     }else{
38       echo '库存减少失败';
39         insertLog('库存减少失败');
40     }
41 }else{
42   echo '库存不够';
43     insertLog('库存不够');
44 }
45 fclose($fp);
46  ?>
复制代码
复制代码

5. 乐观锁思路

这个时候,我们就可以讨论一下“乐观锁”的思路了。乐观锁,是相对于“悲观锁”采用更为宽松的加锁机制,大都是采用带版本号(Version)更新。实现就是,这个数据所有请求都有资格去修改,但会获得一个该数据的版本号,只有版本号符合的才能更新成功,其他的返回抢购失败。这样的话,我们就不需要考虑队列的问题,不过,它会增大CPU的计算开销。但是,综合来说,这是一个比较好的解决方案。

14834077835.jpg

有很多软件和服务都“乐观锁”功能的支持,例如Redis中的watch就是其中之一。通过这个实现,我们保证了数据的安全。

优化方案5:Redis中的watch

复制代码
复制代码
 1 <?php
 2 $redis = new redis();
 3  $result = $redis->connect('127.0.0.1', 6379);
 4  echo $mywatchkey = $redis->get("mywatchkey");
 5 /*
 6   //插入抢购数据
 7  if($mywatchkey>0)
 8  {
 9      $redis->watch("mywatchkey");
10   //启动一个新的事务。
11     $redis->multi();
12    $redis->set("mywatchkey",$mywatchkey-1);
13    $result = $redis->exec();
14    if($result) {
15       $redis->hSet("watchkeylist","user_".mt_rand(1,99999),time());
16       $watchkeylist = $redis->hGetAll("watchkeylist");
17         echo "抢购成功!<br/>";
18         $re = $mywatchkey - 1;  
19         echo "剩余数量:".$re."<br/>";
20         echo "用户列表:<pre>";
21         print_r($watchkeylist);
22    }else{
23       echo "手气不好,再抢购!";exit;
24    } 
25  }else{
26      // $redis->hSet("watchkeylist","user_".mt_rand(1,99999),"12");
27      //  $watchkeylist = $redis->hGetAll("watchkeylist");
28         echo "fail!<br/>";   
29         echo ".no result<br/>";
30         echo "用户列表:<pre>";
31       //  var_dump($watchkeylist); 
32  }*/
33 $rob_total = 100;   //抢购数量
34 if($mywatchkey<=$rob_total){
35     $redis->watch("mywatchkey");
36     $redis->multi(); //在当前连接上启动一个新的事务。
37     //插入抢购数据
38     $redis->set("mywatchkey",$mywatchkey+1);
39     $rob_result = $redis->exec();
40     if($rob_result){
41          $redis->hSet("watchkeylist","user_".mt_rand(1, 9999),$mywatchkey);
42         $mywatchlist = $redis->hGetAll("watchkeylist");
43         echo "抢购成功!<br/>";
44       
45         echo "剩余数量:".($rob_total-$mywatchkey-1)."<br/>";
46         echo "用户列表:<pre>";
47         var_dump($mywatchlist);
48     }else{
49           $redis->hSet("watchkeylist","user_".mt_rand(1, 9999),'meiqiangdao');
50         echo "手气不好,再抢购!";exit;
51     }
52 }
53 ?>
复制代码
复制代码

生活步步是坎坷,笑到最后是大哥。

mysql联合索引

命名规则:表名_字段名
1、需要加索引的字段,要在where条件中
2、数据量少的字段不需要加索引
3、如果where条件中是OR关系,加索引不起作用
4、符合最原则

https://segmentfault.com/q/1010000003984016/a-1020000003984281

联合索引又叫复合索引。对于复合索引:Mysql从左到右的使用索引中的字段,一个查询可以只使用索引中的一部份,但只能是最左侧部分。例如索引是key index (a,b,c). 可以支持a | a,ba,b,c 3种组合进行查找,但不支持 b,c进行查找 .当最左侧字段是常量引用时,索引就十分有效。


两个或更多个列上的索引被称作复合索引。
利用索引中的附加列,您可以缩小搜索的范围,但使用一个具有两列的索引 不同于使用两个单独的索引。
复合索引的结构与电话簿类似,人名由姓和名构成,电话簿首先按姓氏对进行排序,然后按名字对有相同姓氏的人进行排序。
如果您知 道姓,电话簿将非常有用;
如果您知道姓和名,电话簿则更为有用,
但如果您只知道名不姓,电话簿将没有用处

所以说创建复合索引时,应该仔细考虑列的顺序。对索引中的所有列执行搜索或仅对前几列执行搜索时,复合索引非常有用;仅对后面的任意列执行搜索时,复合索引则没有用处。

http://blog.csdn.net/lmh12506/article/details/8879916

当一个表有多条索引可走时,  Mysql  根据查询语句的成本来选择走哪条索引, 联合索引的话, 它往往计算的是第一个字段(最左边那个), 这样往往会走错索引. 如: 
索引Index_1(Create_Time, Category_ID), Index_2(Category_ID) 

如果每天的数据都特别多, 而且有很多category, 但具体每个category的记录不会很多.

当查询SQL条件为select …where create_time ….and category_id=..时, 很可能不走索引Index_1, 而走索引Index_2, 导致查询比较慢.

解决办法是将索引字段的顺序调换一下.

http://www.cnblogs.com/krisy/archive/2013/07/12/3186258.html创建索引

在执行CREATE TABLE语句时可以创建索引,也可以单独用CREATE INDEX或ALTER TABLE来为表增加索引。1.ALTER TABLE

ALTER TABLE用来创建普通索引、UNIQUE索引或PRIMARY KEY索引。

ALTER TABLE table_name ADD INDEX index_name (column_list)

ALTER TABLE table_name ADD UNIQUE (column_list)

ALTER TABLE table_name ADD PRIMARY KEY (column_list)

其中table_name是要增加索引的表名,column_list指出对哪些列进行索引,多列时各列之间用逗号分隔。索引名index_name可选,缺省时,MySQL将根据第一个索引列赋一个名称。另外,ALTER TABLE允许在单个语句中更改多个表,因此可以在同时创建多个索引。2.CREATE INDEX

CREATE INDEX可对表增加普通索引或UNIQUE索引。

CREATE INDEX index_name ON table_name (column_list)

CREATE UNIQUE INDEX index_name ON table_name (column_list)

table_name、index_name和column_list具有与ALTER TABLE语句中相同的含义,索引名不可选。另外,不能用CREATE INDEX语句创建PRIMARY KEY索引。3.索引类型

在创建索引时,可以规定索引能否包含重复值。如果不包含,则索引应该创建为PRIMARY KEY或UNIQUE索引。对于单列惟一性索引,这保证单列不包含重复的值。对于多列惟一性索引,保证多个值的组合不重复。

PRIMARY KEY索引和UNIQUE索引非常类似。
事实上,PRIMARY KEY索引仅是一个具有名称PRIMARY的UNIQUE索引。这表示一个表只能包含一个PRIMARY KEY,因为一个表中不可能具有两个同名的索引。

下面的SQL语句对students表在sid上添加PRIMARY KEY索引。

ALTER TABLE students ADD PRIMARY KEY (sid)4.  删除索引

可利用ALTER TABLE或DROP INDEX语句来删除索引。类似于CREATE INDEX语句,DROP INDEX可以在ALTER TABLE内部作为一条语句处理,语法如下。

DROP INDEX index_name ON talbe_name

ALTER TABLE table_name DROP INDEX index_name

ALTER TABLE table_name DROP PRIMARY KEY

其中,前两条语句是等价的,删除掉table_name中的索引index_name。

第3条语句只在删除PRIMARY KEY索引时使用,因为一个表只可能有一个PRIMARY KEY索引,因此不需要指定索引名。如果没有创建PRIMARY KEY索引,但表具有一个或多个UNIQUE索引,则MySQL将删除第一个UNIQUE索引。

如果从表中删除了某列,则索引会受到影响。对于多列组合的索引,如果删除其中的某列,则该列也会从索引中删除。如果删除组成索引的所有列,则整个索引将被删除。

5.查看索引

mysql> show index from tblname;mysql> show keys from tblname;

  · Table

  表的名称。

  · Non_unique

  如果索引不能包括重复词,则为0。如果可以,则为1。

  · Key_name

  索引的名称。

  · Seq_in_index

  索引中的列序列号,从1开始。

  · Column_name

  列名称。

  · Collation

  列以什么方式存储在索引中。在MySQL中,有值‘A’(升序)或NULL(无分类)。

  · Cardinality

  索引中唯一值的数目的估计值。通过运行ANALYZE TABLE或myisamchk -a可以更新。基数根据被存储为整数的统计数据来计数,所以即使对于小型表,该值也没有必要是精确的。基数越大,当进行联合时,MySQL使用该索引的机会就越大。

  · Sub_part

  如果列只是被部分地编入索引,则为被编入索引的字符的数目。如果整列被编入索引,则为NULL。

  · Packed

  指示关键字如何被压缩。如果没有被压缩,则为NULL。

  · Null

  如果列含有NULL,则含有YES。如果没有,则该列含有NO。

  · Index_type

  用过的索引方法(BTREE, FULLTEXT, HASH, RTREE)。

  · Comment

6.什么情况下使用索引
       表的主关键字

自动建立唯一索引

如zl_yhjbqk(用户基本情况)中的hbs_bh(户标识编号)

表的字段唯一约束

ORACLE利用索引来保证数据的完整性

如lc_hj(流程环节)中的lc_bh+hj_sx(流程编号+环节顺序)

直接条件查询的字段

在SQL中用于条件约束的字段

如zl_yhjbqk(用户基本情况)中的qc_bh(区册编号)

select * from zl_yhjbqk where qc_bh=’<????甼曀???>7001’

查询中与其它表关联的字段

字段常常建立了外键关系

如zl_ydcf(用电成份)中的jldb_bh(计量点表编号)

select * from zl_ydcf a,zl_yhdb b where a.jldb_bh=b.jldb_bh and b.jldb_bh=’540100214511’

查询中排序的字段

排序的字段如果通过索引去访问那将大大提高排序速度

select * from zl_yhjbqk order by qc_bh(建立qc_bh索引)

select * from zl_yhjbqk where qc_bh=’7001’ order by cb_sx(建立qc_bh+cb_sx索引,注:只是一个索引,其中包括qc_bh和cb_sx字段)

查询中统计或分组统计的字段

select max(hbs_bh) from zl_yhjbqk

select qc_bh,count(*) from zl_yhjbqk group by qc_bh

什么情况下应不建或少建索引

表记录太少

如果一个表只有5条记录,采用索引去访问记录的话,那首先需访问索引表,再通过索引表访问数据表,一般索引表与数据表不在同一个数据块,这种情况下ORACLE至少要往返读取数据块两次。而不用索引的情况下ORACLE会将所有的数据一次读出,处理速度显然会比用索引快。

如表zl_sybm(使用部门)一般只有几条记录,除了主关键字外对任何一个字段建索引都不会产生性能优化,实际上如果对这个表进行了统计分析后ORACLE也不会用你建的索引,而是自动执行全表访问。如:

select * from zl_sybm where sydw_bh=’5401’(对sydw_bh建立索引不会产生性能优化)

经常插入、删除、修改的表

对一些经常处理的业务表应在查询允许的情况下尽量减少索引,如zl_yhbm,gc_dfss,gc_dfys,gc_fpdy等业务表。

数据重复且分布平均的表字段

假如一个表有10万行记录,有一个字段A只有T和F两种值,且每个值的分布概率大约为50%,那么对这种表A字段建索引一般不会提高数据库的查询速度。

经常和主字段一块查询但主字段索引值比较多的表字段

如gc_dfss(电费实收)表经常按收费序号、户标识编号、抄表日期、电费发生年月、操作 标志来具体查询某一笔收款的情况,如果将所有的字段都建在一个索引里那将会增加数据的修改、插入、删除时间,从实际上分析一笔收款如果按收费序号索引就已 经将记录减少到只有几条,如果再按后面的几个字段索引查询将对性能不产生太大的影响。

对千万级MySQL数据库建立索引的事项及提高性能的手段

一、注意事项:

首先,应当考虑表空间和磁盘空间是否足够。我们知道索引也是一种数据,在建立索引的时候势必也会占用大量表空间。因此在对一大表建立索引的时候首先应当考虑的是空间容量问题。

其次,在对建立索引的时候要对表进行加锁,因此应当注意操作在业务空闲的时候进行。

二、性能调整方面:

首当其冲的考虑因素便是磁盘I/O。物理上,应当尽量把索引与数据分散到不同的磁盘上(不考虑阵列的情况)。逻辑上,数据表空间与索引表空间分开。这是在建索引时应当遵守的基本准则。

其次,我们知道,在建立索引的时候要对表进行全表的扫描工作,因此,应当考虑调大初始化参数db_file_multiblock_read_count的值。一般设置为32或更大。

再次,建立索引除了要进行全表扫描外同时还要对数据进行大量的排序操作,因此,应当调整排序区的大小。

    9i之前,可以在session级别上加大sort_area_size的大小,比如设置为100m或者更大。

    9i以后,如果初始化参数workarea_size_policy的值为TRUE,则排序区从pga_aggregate_target里自动分配获得。

最后,建立索引的时候,可以加上nologging选项。以减少在建立索引过程中产生的大量redo,从而提高执行的速度。

MySql在建立索引优化时需要注意的问题

设计好MySql的索引可以让你的数据库飞起来,大大的提高数据库效率。设计MySql索引的时候有一下几点注意:

1,创建索引

对于查询占主要的应用来说,索引显得尤为重要。很多时候性能问题很简单的就是因为我们忘了添加索引而造成的,或者说没有添加更为有效的索引导致。如果不加

索引的话,那么查找任何哪怕只是一条特定的数据都会进行一次全表扫描,如果一张表的数据量很大而符合条件的结果又很少,那么不加索引会引起致命的性能下降。
但是也不是什么情况都非得建索引不可,比如性别可能就只有两个值,建索引不仅没什么优势,还会影响到更新速度,这被称为过度索引。

2,复合索引

比如有一条语句是这样的:select * from users where area=’beijing’ and age=22;

如果我们是在area和age上分别创建单个索引的话,由于mysql查询每次只能使用一个索引,所以虽然这样已经相对不做索引时全表扫描提高了很多效

率,但是如果在area、age两列上创建复合索引的话将带来更高的效率。如果我们创建了(area, age,salary)的复合索引,那么其实相当于创建了(area,age,salary)、(area,age)、(area)三个索引,这被称为最佳左前缀特性。
因此我们在创建复合索引时应该将最常用作限制条件的列放在最左边,依次递减。

3,索引不会包含有NULL值的列

只要列中包含有NULL值都将不会被包含在索引中,复合索引中只要有一列含有NULL值,那么这一列对于此复合索引就是无效的。所以我们在数据库设计时不要让字段的默认值为NULL。

4,使用短索引

对串列进行索引,如果可能应该指定一个前缀长度。例如,如果有一个CHAR(255)的 列,如果在前10 个或20 个字符内,多数值是惟一的,那么就不要对整个列进行索引。短索引不仅可以提高查询速度而且可以节省磁盘空间和I/O操作。

5,排序的索引问题

mysql查询只使用一个索引,因此如果where子句中已经使用了索引的话,那么order by中的列是不会使用索引的。因此数据库默认排序可以符合要求的情况下不要使用排序操作;尽量不要包含多个列的排序,如果需要最好给这些列创建复合索引

6,like语句操作

一般情况下不鼓励使用like操作,如果非使用不可,如何使用也是一个问题。like “%aaa%” 不会使用索引而like “aaa%”可以使用索引。

7,不要在列上进行运算

select * from users where

YEAR(adddate)

8,不使用NOT IN

NOT IN都不会使用索引将进行全表扫描。NOT IN可以NOT EXISTS代替

http://www.cnblogs.com/alazalazalaz/p/4083696.html