比特币挖矿挖矿算法

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

区块头

这里写图片描述

首先挖矿算法的目标对象只是区块中的区块头,共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 ?>
复制代码
复制代码

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

fpm与swoole区别

PHP-FPM

早期版本的 PHP 并没有内置的 WEB 服务器,而是提供了 SAPI(Server API)给第三方做对接。现在非常流行的 php-fpm 就是通过 FastCGI 协议来处理 PHP 与第三方 WEB 服务器之间的通信。 (推荐学习: swoole视频教程)

比如 Nginx + php-fpm 的组合,这种方式运行的 fpm 是 Master/Worker 模式,启动一个 Master 进程监听来自 Nginx 的请求,再 fork 多个 Worker 进程处理请求。每个 Worker 进程只能处理一个请求,单一进程的生命周期大体如下:

初始化模块。

初始化请求。此处请求是请求 PHP 执行代码的意思,并非 HTTP 的请求。

执行 PHP 脚本。

结束请求。

关闭模块。

Swoole 采用的也是 Master/Worker 模式,不同的是 Master 进程有多个 Reactor 线程,Master 只是一个事件发生器,负责监听 Socket 句柄的事件变化。Worker 以多进程的方式运行,接收来自 Reactor 线程的请求,并执行回调函数(PHP 编写的)。启动 Master 进程的流程大致是:

初始化模块。

初始化请求。因为 swoole 需要通过 cli 的方式运行,所以初始化请求时,不会初始化 PHP 的全局变量,如 $_SERVER, $_POST, $_GET 等。

执行 PHP 脚本。包括词法、语法分析,变量、函数、类的初始化等,Master 进入监听状态,并不会结束进程。

Swoole 加速的原理

由 Reactor(epoll 的 IO 复用方式)负责监听 Socket 句柄的事件变化,解决高并发问题。

通过内存常驻的方式节省 PHP 代码初始化的时间,在使用笨重的框架时,用 swoole 加速效果是非常明显的。

对比不同

PHP-FPM

Master 主进程 / Worker 多进程模式。

启动 Master,通过 FastCGI 协议监听来自 Nginx 传输的请求。

每个 Worker 进程只对应一个连接,用于执行完整的 PHP 代码。

PHP 代码执行完毕,占用的内存会全部销毁,下一次请求需要重新再进行初始化等各种繁琐的操作。

只用于 HTTP Server。

Swoole

Master 主进程(由多个 Reactor 线程组成)/ Worker 多进程(或多线程)模式

启动 Master,初始化 PHP 代码,由 Reactor 监听 Socket 句柄的事件变化。

Reactor 主线程负责子多线程的均衡问题,Manager 进程管理 Worker 多进程,包括 TaskWorker 的进程。

每个 Worker 接受来自 Reactor 的请求,只需要执行回调函数部分的 PHP 代码。

只在 Master 启动时执行一遍 PHP 初始化代码,Master 进入监听状态,并不会结束进程。

不仅可以用于 HTTP Server,还可以建立 TCP 连接、WebSocket 连接。

以上就是fpm与swoole区别的详细内容,更多请关注php中文网其它相关文章!

Allowed memory size of 134217728 bytes exhausted

一段PHP程序执行报错:

Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 2611816 bytes)

去百度了一下,原来是php.ini中的内存分配的问题,默认php代码能够申请到的最大内存字节数就是134217728 bytes,如果代码执行的时候再需要更多的内存,就会报错了,于是就将php.ini文件中的配置改了一下:

代码如下:

memory_limit = 128M;//将128M改成了256M

php实现单笔转账到支付宝功能

本文实例为大家分享了php实现单笔转账到支付宝的具体代码,供大家参考,具体内容如下

1.首先 去蚂蚁金服签约 单笔转账到支付宝

官方api文档?

2.需要的配置信息

1).应用appid

2).生成密钥

文档地址

根据文档步骤生成

上传这里的 应用公钥

3.下载官方sdk 然后集成到自己项目

服务端SDK

官方实例

//实例化客户端
AlipayClient alipayClient = new DefaultAlipayClient("https://openapi.alipay.com/gateway.do", APP_ID, APP_PRIVATE_KEY, "json", CHARSET, ALIPAY_PUBLIC_KEY, "RSA2");
//实例化具体API对应的request类,类名称和接口名称对应,当前调用接口名称:alipay.open.public.template.message.industry.modify 
AlipayOpenPublicTemplateMessageIndustryModifyRequest request = new AlipayOpenPublicTemplateMessageIndustryModifyRequest();
//SDK已经封装掉了公共参数,这里只需要传入业务参数
//此次只是参数展示,未进行字符串转义,实际情况下请转义
request.setBizContent(" {" +
" "primary_industry_name":"IT科技/IT软件与服务"," +
" "primary_industry_code":"10001/20102"," +
" "secondary_industry_code":"10001/20102"," +
" "secondary_industry_name":"IT科技/IT软件与服务"" +
" }");
AlipayOpenPublicTemplateMessageIndustryModifyResponse response = alipayClient.execute(request); 
//调用成功,则处理业务逻辑
if(response.isSuccess()){
 //.....
}

效果如下

我的代码

<?php
/**
* create by 适可而止
* create time 2018/4/8
*/
namespace OrgUtil;
class AlipayTransfer{
private $appId = 'appid';
private $rsaPrivateKey = '私钥';
private $alipayrsaPublicKey = "支付宝公钥";
private $payer_name = "xx科技";
private $aop;
public function __construct()
{
$g_alipay = C('ALIPAY_CONFIG');
$this- appId = $g_alipay['APPID'];//appid
$this- rsaPrivateKey = $g_alipay['rsaPrivateKey']; //私钥
$this- alipayrsaPublicKey=$g_alipay['rsaPublicKey'];//支付宝公钥
//引入单笔转账sdk
Vendor('Alipayaop.AopSdk');
}
public function init_aop_config()
{
$this- aop- gatewayUrl = 'https://openapi.alipay.com/gateway.do';
$this- aop- appId = $this- appId;
$this- aop- rsaPrivateKey = $this- rsaPrivateKey;
$this- aop- alipayrsaPublicKey=$this- alipayrsaPublicKey;
$this- aop- apiVersion = '1.0';
$this- aop- signType = 'RSA2';
$this- aop- postCharset='UTF-8';
$this- aop- format='json';
}
/**
* 单笔转账接口
* @param $order_number 订单号
* @param $pay_no  转账账号
* @param $pay_name  转账用户名
* @param $amount  转账金额
* @param $memo   备注
*/
public function transfer($order_number,$pay_no,$pay_name,$amount,$memo)
{
//存入转账日志
$this- transferLog($order_number,$pay_no,$pay_name,$amount);
$this- aop = new AopClient ();
//配置参数
$this- init_aop_config();
//导入请求
$request = new AlipayFundTransToaccountTransferRequest ();
$request- setBizContent("{" .
""out_biz_no":"".$order_number.""," .//商户生成订单号
""payee_type":"ALIPAY_LOGONID"," .//收款方支付宝账号类型
""payee_account":"".$pay_no.""," .//收款方账号
""amount":"".$amount.""," .//总金额
""payer_show_name":"".$this- payer_name.""," .//付款方账户
""payee_real_name":"".$pay_name.""," .//收款方姓名
""remark":"".$memo.""" .//转账备注
"}");
$result = $this- aop- execute ( $request);
$responseNode = str_replace(".", "_", $request- getApiMethodName()) . "_response";
$resultCode = $result- $responseNode- code;
$resultSubMsg = $result- $responseNode- sub_msg;
//修改转账日志
$this- edit_transferLog($order_number,$resultCode,$resultSubMsg);
if(!empty($resultCode)&&$resultCode == 10000){
return true;
} else {
return false;
}
}
/**
* 存取日志
*/
private function transferLog($order_number,$pay_no,$pay_name,$amount)
{
$data['order_number'] = $order_number;
$data['pay_no'] = $pay_no;
$data['pay_name'] = $pay_name;
$data['amount'] = $amount;
$data['create_time'] = time();
M('AlipayTransferLog')- add($data);
}
/**
* 修改日志
*/
private function edit_transferLog($order_number,$result_code,$sub_msg)
{
$model = D("AlipayTransferLog");
$where['order_number'] = $order_number;
$result = $model- where($where)- order('create_time desc')- find();
if ($result_code == 10000)
{
$result['status'] = 1;
$sub_msg = 'success';
}
else
{
$result['status'] = 2;
}
$result['memo'] = $sub_msg;
$result['update_time'] = time();
M('AlipayTransferLog')- save($result);
}
/**
* 查单接口
*/
public function query($order_number)
{
$this- aop = new AopClient ();
//配置参数
$this- init_aop_config();
$request = new AlipayFundTransOrderQueryRequest ();
$request- setBizContent("{" .
""out_biz_no":"".$order_number.""" .
" }");
$result = $this- aop- execute ( $request);
$responseNode = str_replace(".", "_", $request- getApiMethodName()) . "_response";
$resultCode = $result- $responseNode- code;
if(!empty($resultCode)&&$resultCode == 10000){
$res_arr['code'] = '00';
$res_arr['data'] = $result;
} else {
$res_arr['code'] = '-1';
}
return $res_arr;
}
}
? 

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持网站事(zalou.cn)。

支付宝app支付服务端接入(证书方式)

支付宝支付接入的文档真的非常多,由于密钥配置错误一直返回4000错误码,折腾了小半个下午,特此记录一下。

整体开发流程图如下:

1 首先创建应用,并签约APP支付能力

官方文档:接入前准备

这个过程需要填写、认证一些公司信息。支付宝签约费率为6%。

2 首先按照官方文档生成并配置公钥证书

参考:如何生成及配置公钥证书 

       首先下载支付宝开放平台开发助手获取CSR文件,选择应用信息内的“接口加签方式”-“公钥证书“-”上传CSR文件”,选择目录支付宝开放平台开发助手/CSR的.csr文件,上传成功后下载“应用公钥证书”(appCertPublicKey_appid数据.crt)、“支付宝公钥证书”(alipayCertPublicKey_RSA2.crt)、“支付宝根证书”(alipayRootCert.crt);

3 服务器端开发

参考:官方文档《JAVA服务端 SDK 生成 APP支付订单信息示例(证书)》

1)在pom.xml中添加最新的alipay sdk依赖

   <dependency>       <groupId>com.alipay.sdk</groupId>       <artifactId>alipay-sdk-java</artifactId>       <version>4.10.29.ALL</version>   </dependency>

2)JAVA服务端 SDK 生成 APP支付订单信息示例(证书)

//构造clientCertAlipayRequest certAlipayRequest = new CertAlipayRequest();//设置网关地址certAlipayRequest.setServerUrl("https://openapi.alipay.com/gateway.do");//设置应用IdcertAlipayRequest.setAppId(app_id);//设置应用私钥certAlipayRequest.setPrivateKey(privateKey);//设置请求格式,固定值jsoncertAlipayRequest.setFormat("json");//设置字符集certAlipayRequest.setCharset(charset);//设置签名类型certAlipayRequest.setSignType(sign_type);//设置应用公钥证书路径certAlipayRequest.setCertPath(app_cert_path);//设置支付宝公钥证书路径certAlipayRequest.setAlipayPublicCertPath(alipay_cert_path);//设置支付宝根证书路径certAlipayRequest.setRootCertPath(alipay_root_cert_path);//构造clientAlipayClient alipayClient = new DefaultAlipayClient(certAlipayRequest); //实例化具体API对应的request类,类名称和接口名称对应,当前调用接口名称:alipay.trade.app.payAlipayTradeAppPayRequest request = new AlipayTradeAppPayRequest();//SDK已经封装掉了公共参数,这里只需要传入业务参数。以下方法为sdk的model入参方式(model和biz_content同时存在的情况下取biz_content)。AlipayTradeAppPayModel model = new AlipayTradeAppPayModel();model.setBody("我是测试数据");model.setSubject("App支付测试Java");model.setOutTradeNo(outtradeno);model.setTimeoutExpress("30m");model.setTotalAmount("0.01");model.setProductCode("QUICK_MSECURITY_PAY");request.setBizModel(model);request.setNotifyUrl("商户外网可以访问的异步地址");try {        //这里和普通的接口调用不同,使用的是sdkExecute        AlipayTradeAppPayResponse response = alipayClient.sdkExecute(request);        System.out.println(response.getBody());//就是orderString 可以直接给客户端请求,无需再做处理。    } catch (AlipayApiException e) {        e.printStackTrace();}

注意的坑:

1)证书和公钥的请求类型不同,证书是CertAlipayRequest;

2)证书和公钥的请求方法不同,证书是sdkExecute方法;

3)app支付时productCode参数的值为 “QUICK_MSECURITY_PAY”;

4)应用私钥指上一步CSR文件夹中的“域名_私钥.txt”文件中的内容,其他三个证书路径则是上一步下载的三个.crt证书的绝对路径。

服务端请求接口成功的话,会返回一个用&拼接的参数字符串,其中有一个签名sign值

4 错误排查

最常见的是ALIN10146错误排查,客户端会返回“系统繁忙,请稍后再试”。

其中最常见的原因是公私钥配对错误,使用证书的话可以参考官方文档《如何检验密钥证书是否匹配》来检验。

微信 H5 支付流程以及一些坑

原文:https://blog.niceue.com/front-end-development/wechat-h5-payment-process-as-well-as-some-pits.html

最近做的 SPA 网站集成了微信支付,使用的是微信 H5 调起支付API接口。做完后对微信 H5 支付的流程有了进一步的了解,在前后端调试接口的过程中也遇到了一些问题,在这里记录下来。

支付流程

  1. 在订单页 ajax 请求后端发起下单,后端挂起请求
  2. 后端根据订单号结合微信支付相关配置参数向微信服务器发起统一下单
  3. 下单成功,微信通知前面传递的 notify_url,返回 prepay_id (预支付交易会话标识)
  4. 后端返回前端 JSAPI 调用的参数
  5. 前端使用 JSSDK 的 wx.chooseWXPay 发起支付
wx.chooseWXPay({
  timestamp: '',   // 支付签名时间戳
  nonceStr: '',    // 支付签名随机串
  package: '',     // 统一支付接口返回的prepay_id参数值,提交格式如:prepay_id=***)
  signType: 'MD5', // 签名方式
  paySign: '',     // 支付签名
})

这里看起来是 5 步,但其实还少了一步。调用“统一下单”接口的时候需要微信用户的 openid。

openid 是什么?

官方解释

加密后的微信号,每个用户对每个公众号的 openid 是唯一的。对于不同公众号,同一用户的 openid 不同

这个 openid 只能在微信环境通过重定向拿到。但是在下单的时候用的是 ajax 请求,还用重定向用户体验就比较差。所以需要在进入微信的时候就通过重定向拿到 openid 缓存起来,后面就可以直接使用了。所以入口页面就要做点小动作。

获取 openid

官方有OpenID的获取指引。对于 H5 应用,获取方式分 3 步走:

1. 后端重定向到获取 code 接口

后端可以定义一个 /wechat/redirect 接口,例如:

http://yoursite.com/wechat/redirect?url=home_url 

用这个地址重定向获取 code 地址,其中的 url 参数为最终的重定向地址,在拿到 openid 后跳转到该 url

2. 获取 code:

接口地址

https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirec

接口参数:

  • appid: 公众账号ID
  • redirect_uri: 接收 code 的回调地址(请UrlEncode)
  • response_type: 固定值 code
  • scope: 应用授权作用域,填 snsapi_base 或者 snsapi_login(可获取用户信息,如头像、昵称等)
  • state: 用于保持请求和回调的状态,防止 csrf 攻击,可设置为简单的随机数加 session 进行校验

提示:snsapi_login 会跳转到授权页让用户授权
微信接着会重定向三次,第三次重定向返回到 redirect_uri 地址,并且带上了 code 参数

3. 通过 code 获取 openid 和 access_token

接口地址

https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code

获取到 openid 后注意在 session 中缓存起来

一些需要注意的坑

1. 获取 code 的接口地址

OpenID的获取指引文档,请注意黑色标题“微信公众平台”和“微信开放平台”。两个地方获取 code 的接口地址不一样,但参数是一样的。最开始后端看错了文档使用的是“网站应用微信登录”文档里面提供的获取 code 的接口,这样子怎么都是调不通的。

// 微信公众平台是
https://open.weixin.qq.com/connect/oauth2/authorize
// 微信开放平台是
https://open.weixin.qq.com/connect/qrconnect

2. 后端签名用 timeStamp,而前端调用支付接口使用全小写 timestamp

微信官方网页端调起支付API这个接口文档参数却误导观众,写的是驼峰的 timeStamp
后端为了方便返回给前端的也是 timeStamp,所以在前端需要转换
解决办法:

if (!data.timestamp) data.timestamp = data.timeStamp

3. iOS 和 Android 版微信对“支付授权目录”的检测不同

http://yousite.com/mobile/#!/checkout

以上路径,iOS 微信识别正确:http://yousite.com/mobile/
而 Android 微信识别出的目录是:http://yousite.com/mobile/#!/checkout
这应该是 Android 版微信的 Bug
解决办法是在 # 前添加一个 ?http://yousite.com/mobile/?#!/checkout

微信h5支付(php版) 2019

1.  登录商户平台–>产品中心–>我的产品–>支付产品–>H5支付(申请开通),

    平台地址:https://pay.weixin.qq.com/index.php/core/home/login?return_url=%2Findex.php

2. 直接上代码,一个php即可搞定,本代码适用于tp5开发,其他框架或语言需要自行修改

3. 修改自己的参数,更多问题请加群:1077573395

<?php
namespace app\index\controller;

use curl\Curl;
use think\Controller;
use think\Request;

class Wxpay extends Controller
{
    /*
     * 1.登录微信支付商户平台,申请h5支付,https://pay.weixin.qq.com/index.php/core/home/login?return_url=%2F
     * 2.获取商户号,商户key,商户key在api设置里面,设置api密钥
     * 3.appid需要授权关联小程序或服务器类得appid
     * */
    public $appid ;//APPID
    public $mch_id;//商户号
    public $key;//商户key
    public $notify_url; //回调url
    public function _initialize()
    {
        $this->appid = 'wx********';
        $this->mch_id = '*******';
        $this->key = '*************';
        $this->notify_url = 'https://*******/wxpay/index';
    }

    //支付成功通知地址
    public function index(){
        $xml = file_get_contents('php://input');//监听是否有数据传入
        if(!empty($xml)){
            //微信返回信息
            $data = $this->xml_to_data($xml);
            if($data['return_code'] == 'SUCCESS'){
                var_dump($data);
            }
        }
    }

    /**
     * 下单方法
     * @param   $params 下单参数
     */
    public function unifiedOrder(){
        $params['body'] = '测试商品'; //商品描述
        $params['out_trade_no'] = date('YmndHis',time()).rand(1111,9999); //订单号
        $params['total_fee'] = 1; //金额是以分为单位,除测试外,需乘以100
        $params['trade_type'] = 'MWEB';    //交易类型,h5支付,默认如此
        $params['scene_info'] = '{"h5_info": {"type":"Wap","wap_url": "https://m.1sheq.cn","wap_name": "上海正拼"}}';   //场景信息,h5固定
        $params['spbill_create_ip'] = $this->getIp();   //终端IP
        $params['appid'] = $this->appid;
        $params['mch_id'] = $this->mch_id;
        $params['nonce_str'] = $this->genRandomString();    //随机字符串
        $params['notify_url'] = $this->notify_url;  //通知地址
        //获取签名数据
        $params['sign'] = $this->MakeSign( $params );   //签名

        $xml = $this->data_to_xml($params);

        $uri = 'https://api.mch.weixin.qq.com/pay/unifiedorder';    //请求地址
        $response = $this->postXmlCurl($uri,$xml);   //自定义封装的xml请求格式,文章最下面为参考postxml
        if( !$response ){
            return false;
        }
        $result = $this->xml_to_data( $response );

        if( !empty($result['result_code']) && !empty($result['err_code']) ){
            $result['err_msg'] = $this->error_code( $result['err_code'] );
        }

        if($result['result_code'] == 'SUCCESS' && $result['return_msg'] == 'OK'){
            //发起微信支付url
            $pay_url = $result['mweb_url'].'&redirect_url='.urlencode($this->notify_url);
            //数据库操作
            $model = new Data();
            $model->addTrade();

            //返回发起支付url,微信外浏览器访问
            return $pay_url;

        }


        //return $result;
    }
    /**
     * 查询订单信息
     * @param $out_trade_no     订单号
     * @return array
     */
    public function orderQuery( $out_trade_no ){
        $params['appid'] = $this->appid;
        $params['mch_id'] = $this->mch_id;
        $params['nonce_str'] = $this->genRandomString();
        $params['out_trade_no'] = $out_trade_no;
        //获取签名数据
        $params['sign'] =  $this->MakeSign($params);
        $xml = $this->data_to_xml($params);
        $uri = 'https://api.mch.weixin.qq.com/pay/orderquery';
        $response = Curl::postXmlCurl($uri,$xml);
        if(!$response){
            return false;
        }
        $result = $this->xml_to_data( $response );
        if( !empty($result['result_code']) && !empty($result['err_code']) ){
            $result['err_msg'] = $this->error_code( $result['err_code'] );
        }
        return $result;
    }
    /**
     * 关闭订单
     * @param $out_trade_no     订单号
     * @return array
     */
    public function closeOrder( $out_trade_no ){
        $params['appid'] = $this->appid;
        $params['mch_id'] = $this->mch_id;
        $params['nonce_str'] = $this->genRandomString();
        $params['out_trade_no'] = $out_trade_no;
        //获取签名数据
        $params['sign'] = $this->MakeSign( $params );
        $xml = $this->data_to_xml($params);
        $response = Curl::postXmlCurl($xml, self::API_URL_PREFIX.self::CLOSEORDER_URL);
        if( !$response ){
            return false;
        }
        $result = $this->xml_to_data( $response );
        return $result;
    }
    /**
     *
     * 获取支付结果通知数据
     * return array
     */
    public function getNotifyData(){
        //获取通知的数据
        $xml = file_get_contents('php://input');
        //echo 123;die;
        $data = array();
        if( empty($xml) ){
            return false;
        }
        $data = $this->xml_to_data( $xml );
        if( !empty($data['return_code']) ){
            if( $data['return_code'] == 'FAIL' ){
                return false;
            }
        }
        return $data;
    }
    /**
     * 接收通知成功后应答输出XML数据
     * @param string $xml
     */
    public function replyNotify(){
        $data['return_code'] = 'SUCCESS';
        $data['return_msg'] = 'OK';
        $xml = $this->data_to_xml( $data );
        echo $xml;
        die();
    }
    /**
     * 生成APP端支付参数
     * @param  $prepayid   预支付id
     */
    public function getAppPayParams( $prepayid ){
        $data['appid'] = $this->appid;
        $data['partnerid'] = $this->mch_id;
        $data['prepayid'] = $prepayid;
        $data['package'] = 'Sign=WXPay';
        $data['noncestr'] = $this->genRandomString();
        $data['timestamp'] = time();
        $data['sign'] = $this->MakeSign( $data );
        return $data;
    }
    /**
     * 生成签名
     *  @return 签名
     */
    public function MakeSign( $params ){
        //签名步骤一:按字典序排序数组参数
        ksort($params);
        $string = $this->ToUrlParams($params);
        //签名步骤二:在string后加入KEY
        $string = $string . "&key=".$this->key;
        //签名步骤三:MD5加密
        $string = md5($string);
        //签名步骤四:所有字符转为大写
        $result = strtoupper($string);
        return $result;
    }
    /**
     * 将参数拼接为url: key=value&key=value
     * @param   $params
     * @return  string
     */
    public function ToUrlParams( $params ){
        $string = '';
        if( !empty($params) ){
            $array = array();
            foreach( $params as $key => $value ){
                $array[] = $key.'='.$value;
            }
            $string = implode("&",$array);
        }
        return $string;
    }
    /**
     * 输出xml字符
     * @param   $params     参数名称
     * return   string      返回组装的xml
     **/
    public function data_to_xml( $params ){
        if(!is_array($params)|| count($params) <= 0)
        {
            return false;
        }
        $xml = "<xml>";
        foreach ($params as $key=>$val)
        {
            if (is_numeric($val)){
                $xml.="<".$key.">".$val."</".$key.">";
            }else{
                $xml.="<".$key."><![CDATA[".$val."]]></".$key.">";
            }
        }
        $xml.="</xml>";
        return $xml;
    }
    /**
     * 将xml转为array
     * @param string $xml
     * return array
     */
    public function xml_to_data($xml){
        if(!$xml){
            return false;
        }
        //将XML转为array
        //禁止引用外部xml实体
        libxml_disable_entity_loader(true);
        $data = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
        return $data;
    }
    /**
     * 获取毫秒级别的时间戳
     */
    public static function getMillisecond(){
        //获取毫秒的时间戳
        $time = explode ( " ", microtime () );
        $time = $time[1] . ($time[0] * 1000);
        $time2 = explode( ".", $time );
        $time = $time2[0];
        return $time;
    }
    /**
     * 产生一个指定长度的随机字符串,并返回给用户
     * @param type $len 产生字符串的长度
     * @return string 随机字符串
     */
    public function genRandomString($len = 32) {
        $chars = array(
            "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k",
            "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v",
            "w", "x", "y", "z", "A", "B", "C", "D", "E", "F", "G",
            "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R",
            "S", "T", "U", "V", "W", "X", "Y", "Z", "0", "1", "2",
            "3", "4", "5", "6", "7", "8", "9"
        );
        $charsLen = count($chars) - 1;
        // 将数组打乱
        shuffle($chars);
        $output = "";
        for ($i = 0; $i < $len; $i++) {
            $output .= $chars[mt_rand(0, $charsLen)];
        }
        return $output;
    }

    /**
     * 错误代码
     * @param  $code       服务器输出的错误代码
     * return string
     */
    public function error_code( $code ){
        $errList = array(
            'NOAUTH'                =>  '商户未开通此接口权限',
            'NOTENOUGH'             =>  '用户帐号余额不足',
            'ORDERNOTEXIST'         =>  '订单号不存在',
            'ORDERPAID'             =>  '商户订单已支付,无需重复操作',
            'ORDERCLOSED'           =>  '当前订单已关闭,无法支付',
            'SYSTEMERROR'           =>  '系统错误!系统超时',
            'APPID_NOT_EXIST'       =>  '参数中缺少APPID',
            'MCHID_NOT_EXIST'       =>  '参数中缺少MCHID',
            'APPID_MCHID_NOT_MATCH' =>  'appid和mch_id不匹配',
            'LACK_PARAMS'           =>  '缺少必要的请求参数',
            'OUT_TRADE_NO_USED'     =>  '同一笔交易不能多次提交',
            'SIGNERROR'             =>  '参数签名结果不正确',
            'XML_FORMAT_ERROR'      =>  'XML格式错误',
            'REQUIRE_POST_METHOD'   =>  '未使用post传递参数 ',
            'POST_DATA_EMPTY'       =>  'post数据不能为空',
            'NOT_UTF8'              =>  '未使用指定编码格式',
        );
        if( array_key_exists( $code , $errList ) ){
            return $errList[$code];
        }
    }


    //xml请求
     public function postXmlCurl($url,$xml,$second = 30){
        $ch = curl_init();
        //设置超时
        curl_setopt($ch, CURLOPT_TIMEOUT, $second);
        curl_setopt($ch,CURLOPT_URL, $url);
        curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,FALSE);
        curl_setopt($ch,CURLOPT_SSL_VERIFYHOST,FALSE);
        //设置 header
        curl_setopt($ch, CURLOPT_HEADER, FALSE);
        //要求结果为字符串且输出到屏幕上
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
        //post 提交方式
        curl_setopt($ch, CURLOPT_POST, TRUE);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
        //运行 curl
        $data = curl_exec($ch);
        //返回结果
        if($data){
            curl_close($ch);
            return $data;
        }else{
            $error = curl_errno($ch);
            curl_close($ch);
            echo "curl 出错,错误码:$error"."<br>";
        }
    }


    //获取用户真实ip,此处为tp5所用,其他自行修改
    public function getIp(){
        $request = Request::instance();
        $ip  = $request->ip();
        return $ip;
    }



    }