[转载]缓存系列文章(三)缓存常用更新策略对比(一致性)。

转载出处:http://carlosfu.iteye.com/blog/2269678

 一、缓存的几种更新策略

 

从下面的表格看,缓存的更新策略大致分为三种,本文将从一致性和维护成本两个方面对于三种缓存更新策略进行简要说明,因为这些东西比较理论和抽象,如哪里说得不对,欢迎拍砖。

 

注:

(1) 一致性:缓存和真实数据源(例如mysql, hbase, elasticsearch等等)是否存在一段时间数据的不一致。

(2) 维护成本: 开发人员的开发和维护成本。

策略 一致性 维护成本
LRU/LIRS/FIFO算法剔除 最差
超时剔除 较差 较低
主动更新

 

二、LRU/LFU/FIFO算法剔除

 

1. 使用场景:

通常用于缓存使用量超过了预设的最大值时候(缓存空间不够),如何对现有的数据进行清理。例如FIFO会把最新进入缓存的数据清理出去, LRU会把最近最少使用的数据清理掉。

例如:Memcache使用的是LRU,具体Memcache如何实现的,这里就不在赘述了,网上资料多的是。

例如:Redis使用maxmemory-policy这个配置作为内存最大值后对于数据的更新策略。

配置名 含义 默认值
maxmemory 最大可用内存 不使用该配置,也就对内存使用无限制
maxmemory-policy 内存不够时,淘汰策略 volatile-lru
  • volatile-lru -> 用lru算法删除过期的键值
  • allkeys-lru -> 用lru算法删除所有键值
  • volatile-random -> 随机删除过期的键值
  • allkeys-random -> 随机删除任何键值
  • volatile-ttl -> 删除最近要到期的键值
  • noeviction -> 不删除键,只返回一个错误

 

2. 常用算法:

这里不再赘述,常用的算法有如下几种:

FIFO[first in first out]

LFU[Less Frequently Used]

LRU[Least Recently used]

 

 

3. 一致性

可以想象,要清理哪些数据,不是由开发者决定(只能决定大致方向:策略算法),数据的一致性是最差的。

 

4. 维护成本

这些算法不需要开发者自己来实现,通常只需要配置最大maxmemory和对应的策略即可。

开发者只需要有这个东西,知道是什么意思,选择自己需要的算法,算法的实现是由缓存服务器实现的。

 

 

三、超时剔除

 

1. 使用场景:

   就是我们通常做的缓存数据过期时间设置,例如redis和memcache都提供了expire这样的API,来设置K-V的过期时间。

一般来说业务可以容忍一段时间内(例如一个小时),缓存数据和真实数据(例如:mysql, hbase等等)数据不一致(一般来说,缓存可以提高访问速度降低后端负载),那么我们可以对一个数据设置一定时间的过期时间,在数据过期后,再从真实数据源获取数据,重新放到缓存中,继续设置过期时间。

例如: 一个视频的描述信息,我们可以容忍一个小时内数据不一致,但是涉及到钱的方面,如果不一致可想而知。

   

2. 一致性:

    一段时间内(取决于过期时间)存在数据一致性问题,即缓存数据和真实数据源数据不一致。

 

3. 维护成本

      用户的维护成本不是很高,只需要设置expire过期时间即可(前提是你的业务允许这段时间可能发生的数据不一致)。

 

四、主动更新

 

1. 使用背景:

   业务对于数据的一致性要求很高,需要在真实数据更新后,立即更新缓存数据。

具体做法:例如可以利用消息系统或者其他方式(比如数据库触发器,或者其他数据源的listener机制来完成)通知缓存更新。

 

2.  一致性:

   可以想象一致性最高(几乎接近强一致),但是有个问题:如果主动更新发生了问题,那么这条数据很可能很长时间不会更新了(所以可以结合超时剔除一起使用,下面最佳实践会说到)

 

3. 维护成本:

相当高,用户需要自己来完成更新(需要一定量的代码,从某种程度上加大了系统的复杂性),需要自己检查数据是否真的更新了之类的工作。

 

五、最佳实践

    其实最佳实践就是组合使用:

1. 一般来说我们都需要配置超过最大缓存后的更新策略(例如:LRU)以及最大内存,这样可以保证系统可以继续运行(例如redis可能存在OOM问题)(极端情况下除外,数据一致性要求极高)。

2. 一般来说我们需要把超时剔除和主动更新组合使用,那样即使主动更新出了问题,也能保证过期时间后,缓存就被清除了(不至于永远都是脏数据)。

[转载]缓存使用与设计系列文章(二)是否真的需要缓存?

转载出处:http://carlosfu.iteye.com/blog/2269678

一、缓存的成本和收益是什么:

 

既然要讨论是否真的需要缓存这个问题,就要知道缓存带来的成本与收益(好处、坏处)是什么?

收益 成本
缓存 + 后端存储(资源) 1. 加速读写

2. 降低后端负载

1. 数据不一致性

2. 代码维护成本

3. 架构复杂度

 

上面的表格应该清楚的表达了使用缓存后的收益和成本分别是什么。下面将进行详细的解析

 

二、缓存成本与收益详解:

 

1. 收益是很明显的,通常来说一个设计还不错的缓存系统,能够帮助你的业务实现加速读写,同时帮助降低了后端负载。

(1) 加速读写:通常来说加速是明显的,因为缓存通常都是全内存的系统,而后端(可能是mysql、甚至是别人的HTTP, RPC接口)都有速度慢和抗压能力差的特性,通过缓存的使用可以有效的提高用户的访问速度同时优化了用户的体验。

(2) 降低后端负载:通过缓存的添加,如果程序没有什么问题,在命中率还可以的情况下,可以帮助后端减少访问量和复杂计算(join、或者无法在优化的sql等),在很大程度降低了后端的负载。

2. 成本:

(1) 数据不一致性:无论你的设计做的多么好,缓存数据与权威数据源(可以理解成真实或者后端数据源)一定存在着一定时间窗口的数据不一致性,这个时间窗口的大小可大可小,具体多大还要看一下你的业务允许多大时间窗口的不一致性。

(2) 代码维护成本:加入缓存后,代码就会在原数据源基础上加入缓存的相关代码,例如原来只是一些sql, 现在要加入k-v缓存,必然增加了代码的维护成本。

(3) 架构复杂度:加入缓存后,例如加入了redis-cluster,一般来说缓存不会像Mysql有专门的DBA,很有可能没有专职的管理人员,所以也增加了架构的复杂度和维护成本。

 

三、如何选择?

 

如果当前系统的访问速度和访问量能够满足现有的要求,就不必增加缓存,其实像mysql并没有那么差,一台运行良好的Mysql,扛个QPS=1000没什么问题。

如果要加入选择了缓存,一定要能给出足够的理由,不是为了简单的show技术和想当然,最好的方法就是用数据说话:加速比有多少、后端负载降低了多少。

 

四、什么样的场景需要缓存?

 

在公司里,据我观察,无论怎么更新架构,使用各种新技术,但是80%的项目还是离不开SQL的,下面我们以SQL作为后端数据源、以Redis作为缓存层,说一下哪些场景是需要缓存的。

1、复杂开销大的计算、降低后端负载

以Mysql为例子,一些复杂的操作或者计算(例如大量联表操作、一些分组计算),如果不加

缓存,大量流量将在这些复杂计算的执行。

 

2. 加速请求响应

即使单条后端数据足够快(例如select * from table where id=?),那么依然可以利用redis/memcache将这些操作进行merge做优化(例如:cache(select * from table where id in(id1,id10….idK))),从而优化整个IO链的相应时间。

 

 

 

附图一张:

[转载]缓存使用与设计系列文章(一)缓存的一些基本常识。

 一、基本概念

 

1. Cache(缓存): 从cpu的一级和二级缓存、Internet的DNS、到浏览器缓存都可以看做是一种缓存。

维基百科: 写道
a store of things that will be required in the future, and can be retrieved rapidly.
(存贮数据(使用频繁的数据)的临时地方,因为取原始数据的代价太大了,所以我可以取得快一些)


 

2. Cache hit(缓存命中)(下图左)

When a data element is requested from cache and the elements exists for the given key.

3. Cahe miss(缓存未命中): 与Cache hit相反(下图右)

4. 缓存算法:缓存容量超过预设,如何踢掉“无用”的数据。

例如:LRU(Least Recently Used) FIFO(First Input First Output)Least Frequently Used(LFU) 等等

5. System-of-Record(真实数据源): 例如关系型数据库、其他持久性系统等等。

也有英文书叫做authority data(权威数据)

 

6. serialization-and-deserialization(序列化与反序列化):可以参考:序列化与反序列化(美团工程师写的,非常棒的文章)

后面也有单独文章去分析。


 

6. Scale Up (垂直扩容) 和 Scale out (水平扩容)

 

驴拉车,通常不是把一头驴养壮(有极限),而通常是一群驴去拉(当然每个个体也不能太差)。

 

 

服务器也是一样的道理,至少互联网是这样:

7. Write-through 和 write-behind

 

8.阿姆而达定律:用于计算缓存加速比

 

 

二、缓存的种类或者类型

 

1. LocalCache(独立式): 例如Ehcache、BigMemory Go

(1) 缓存和应用在一个JVM中。

(2) 缓存间是不通信的,独立的。

(3) 弱一致性。

 

2. Standalone(单机):

(1) 缓存和应用是独立部署的。

(2) 缓存可以是单台。(例如memcache/redis单机等等)

(3) 强一致性

(4) 无高可用、无分布式。

(5) 跨进程、跨网络

 

3. Distributed(分布式):例如Redis-Cluster, memcache集群等等

(1) 缓存和应用是独立部署的。

(2) 多个实例。(例如memcache/redis等等)

(3) 强一致性或者最终一致性

(4) 支持Scale Out、高可用。

(5) 跨进程、跨网络

 

4. Replicated(复制式): 缓存数据时同时存放在多个应用节点的,数据复制和失效的事件以同步或者异步的形式在各个集群节点间传播。(也是弱一致性)

这种用的不太多。

 

 

三、数据层访问速度:(作为开发人员要记住这些量级)

 

PHP合并数组操作“”+”与array_merge的区别

这个也算是自己在编码中碰到的一个坑

1、键名为数字(数字型字符串)时,array_merge()不会覆盖掉原来的值,但“+”合并数组则会把最先出现的值作为最终结果返回,而把后面的数组拥有相同键名的那些值“抛弃”掉

$arr1 = array('a','b');
$arr2 = array('c', 'd');
var_dump($arr1+$arr2);
//输出
array(2) {
[0]=>
string(1) "a"
[1]=>
string(1) "b"
}
var_dump(array_merge($arr1, $arr2));
//输出
array(4) {
[0]=>
string(1) "a"
[1]=>
string(1) "b"
[2]=>
string(1) "c"
[3]=>
string(1) "d"
}

2、键名为字符(非数字型字符)时,+仍然把最先出现的值作为最终结果返回,而把后面的数组拥有相同键名的那些值“抛弃”掉,但array_merge()此时会覆盖掉前面相同键名的值
$arr1 = array(0 => 'a', 'ab' => 'b');
$arr2 = array('0' => 'c', 'ab' => 'd');
var_dump($arr1+$arr2);
//输出
array(2) {
[0]=>
string(1) "a"
["ab"]=>
string(1) "b"
}
var_dump(array_merge($arr1, $arr2));
array(3) {
[0]=>
string(1) "a"
["ab"]=>
string(1) "d"
[1]=>
string(1) "c"
}


 

 

[转载]PHP反射(1)

PHP反射

注:文章转载自http://www.laravel-vue.xyz

反射介绍

PHP 反射机制,对类、接口、函数、方法和扩展进行反向工程的能力。
分析类,接口,函数和方法的内部结构,方法和函数的参数,以及类的属性和方法。

反射中常用的几个类:

  • ReflectionClass 解析类
  • ReflectionProperty 类的属性的相关信息
  • ReflectionMethod 类方法的有关信息
  • ReflectionParameter 取回了函数或方法参数的相关信息
  • ReflectionFunction 一个函数的相关信息

分析类:

class Student
{
    public $id;
    
    public $name;

    const MAX_AGE = 200;

    public static $likes = [];

    public function __construct($id, $name = 'li')
    {
        $this->id = $id;
        $this->name = $name;
    }

    public function study()
    {
        echo 'learning...';
    }

    private function _foo()
    {
        echo 'foo';
    }

    protected function bar($to, $from = 'zh')
    {
        echo 'bar';
    }
}

 

ReflectionClass

$ref = new ReflectionClass('Student');

// 判断类是否可实例化
if ($ref->isInstantiable()) {
    echo '可实例化';
}

// 获取类构造函数
// 有返回 ReflectionMethod 对象,没有返回 NULL
$constructor = $ref->getConstructor();
print_r($constructor);



// 获取某个属性
if ($ref->hasProperty('id')) {
    $attr = $ref->getProperty('id');
    print_r($attr);
}

// 获取属性列表
$attributes = $ref->getProperties();
foreach ($attributes as $row) {
    // 这里的 $row 为 ReflectionProperty 的实例
    echo $row->getName() , "\n";
}

// 获取静态属性,返回数组
$static = $ref->getStaticProperties();
print_r($static);

// 获取某个常量
if ($ref->hasConstant('MAX_AGE')) {
    $const = $ref->getConstant('MAX_AGE');
    echo $const;
}

// 获取常量,返回数组
$constants = $ref->getConstants();
print_r($constants);

// 获取某个方法
if ($ref->hasMethod('bar')) {
    $method = $ref->getMethod('bar');
    print_r($method);
}

// 获取方法列表
$methods = $ref->getMethods();
foreach ($methods as $key => $value) {
    // 这里的 $row 为 ReflectionMethod 的实例
    echo $value->getName() . "\n";
}

ReflectionProperty

if ($ref->hasProperty('name')) {
    $attr = $ref->getProperty('name');

    // 属性名称
    echo $attr->getName();

    // 类定义时属性为真,运行时添加的属性为假
    var_dump($attr->isDefault());

    // 判断属性访问权限
    var_dump($attr->isPrivate());
    var_dump($attr->isProtected());
    var_dump($attr->isPublic());
    // 判断属性是否为静态
    var_dump($attr->isStatic());
}

RefleactionMethod & ReflectionParameter

if ($ref->hasMethod('bar')) {
    $method = $ref->getMethod('bar');
    echo $method->getName();

    // isAbstract  判断是否是抽象方法
    //isConstructor  判断是否是构造方法
    //isDestructor  判断是否是析构方法
    //isFinal  判断是否是 final 描述的方法
    //isPrivate  判断是否是 private 描述的方法
    //isProtected 判断是否是 protected 描述的方法
    //isPublic 判断是否是 public 描述的方法
    //isStatic 判断是否是 static 描述的方法
    
    // 获取参数列表
    $parameters = $method->getParameters();
    foreach ($parameters as $row) {
        // 这里的 $row 为 ReflectionParameter 实例
        echo $row->getName();
        echo $row->getClass();

        // 检查变量是否有默认值
        if ($row->isDefaultValueAvailable()) {
            echo $row->getDefaultValue();
        }

        // 获取变量类型
        if ($row->hasType()) {
            echo $row->getType();
        }
        
    }
}

ReflectionFunction & ReflectionParameter

$fun = new ReflectionFunction('demo');
echo $fun->getName();
$parameters = $fun->getParameters();
foreach ($parameters as $row) {
    // 这里的 $row 为 ReflectionParameter 实例
    echo $row->getName();
    echo $row->getClass();

    // 检查变量是否有默认值
    if ($row->isDefaultValueAvailable()) {
        echo $row->getDefaultValue();
    }

    // 获取变量类型
    if ($row->hasType()) {
        echo $row->getType();
    }
}

综合实例

下面用一个简单的示例:如果用反射实例化类。
file: Student.php

class Student
{

    public $id;
    
    public $name;

    public function __construct($id, $name)
    {
        $this->id = $id;
        $this->name = $name;
    }

    public function study()
    {
        echo 'learning.....';
    }

}

一般情况下,实例化类的时候,直接使用 new,但是我们现在不用这种方法,我们使用反射来实现。
file: index.php

require 'student.php';

function make($class, $vars = [])
{
    $ref = new ReflectionClass($class);

    // 检查类 Student 是否可实例化
    if ($ref->isInstantiable()) {
        // 获取构造函数
        $constructor = $ref->getConstructor();
        // 没有构造函数的话,直接实例化
        if (is_null($constructor)) {
            return new $class;
        }
        // 获取构造函数参数
        $params = $constructor->getParameters();
        $resolveParams = [];
        foreach ($params as $key => $value) {
            $name = $value->getName();
            if (isset($vars[$name])) {
                // 判断如果是传递的参数,直接使用传递参数
                $resolveParams[] = $vars[$name];
            } else {
                // 没有传递参数的话,检查是否有默认值,没有默认值的话,按照类名进行递归解析
                $default = $value->isDefaultValueAvailable() ? $value->getDefaultValue() : null;
                if (is_null($default)) {
                    if ($value->getClass()) {
                        $resolveParams[] = make($value->getClass()->getName(), $vars);
                    } else {
                        throw new Exception("{$name} 没有传值且没有默认值");
                    }
                } else {
                    $resolveParams[] = $default;
                }
            }
        }

        // 根据参数实例化
        return $ref->newInstanceArgs($resolveParams);
    } else {
        throw new Exception("类 {$class} 不存在!");
    }
}

## 情况一
try {
    $stu = make('Student', ['id' => 1]);
    print_r($stu);
    $stu->study();
} catch (Exception $e) {
    echo $e->getMessage();
}

## 情况二
try {
    $stu = make('Student', ['id' => 1, 'name' => 'li']);
    print_r($stu);
    $stu->study();
} catch (Exception $e) {
    echo $e->getMessage();
}

上面两种情况很明显第一种,缺少参数 name,无法实例化成功,第二种情况就可以实例化成功。

那么我们如果将类 Student 的构造函数修改为:

public function __construct($id, $name = 'zhang')
{
    $this->id = $id;
    $this->name = $name;
}

这样设置 name 有默认值的情况下,那么第一种情况也可以实例化成功。

情况三

第三种情况:如果在类的构造函数中有其他类为参数的情况下,那么也可以解析:

public function __construct($id, $name, Study $study)
{
    $this->id = $id;
    $this->name = $name;
    $this->study = $study;
}

那么这种情况下,在分析类的构造函数参数的时候,如果没有传递参数的话,就会递归调用 make 方法处理 Study 类,如果类存在的话,实例化。

file: study.php

// 我们这里不写构造函数,测试下没有构造函数的情况
class Study
{
    public function show()
    {
        echo 'show';
    }
}

将 Student 类的方法 study 修改为:

1
2
3
4
public function study()
{
    $this->name . ' ' . $this->study->show();
}

下面测试:

try {
    $stu = make('Student', ['id' => 1]);
    print_r($stu);
    $stu->study();
} catch (Exception $e) {
    echo $e->getMessage();
}

PHP 的反射是一个很用的功能,我这里只能很简单的讲解了一点皮毛,详细介绍和用法可参看 官方手册

[转载]php 整洁之道

clean-code-php

Table of Contents

  1. 介绍
  2. 变量
  3. 函数

介绍

本文由 yangweijie 翻译自clen php code,团建用,欢迎大家指正。

摘录自 Robert C. Martin的Clean Code 书中的软件工程师的原则 ,适用于PHP。 这不是风格指南。 这是一个关于开发可读、可复用并且可重构的PHP软件指南。

并不是这里所有的原则都得遵循,甚至很少的能被普遍接受。 这些虽然只是指导,但是都是Clean Code作者多年总结出来的。

Inspired from clean-code-javascript

变量

使用有意义且可拼写的变量名

Bad:

$ymdstr = $moment->format('y-m-d');

Good:

$currentDate = $moment->format('y-m-d');

⬆ 返回顶部

同种类型的变量使用相同词汇

Bad:

getUserInfo();
getClientData();
getCustomerRecord();

Good:

getUser();

⬆ 返回顶部

使用易检索的名称

我们会读比写要多的代码。通过是命名易搜索,让我们写出可读性和易搜索代码很重要。

Bad:

// What the heck is 86400 for?
addExpireAt(86400);

Good:

// Declare them as capitalized `const` globals.
interface DateGlobal {
  const SECONDS_IN_A_DAY = 86400;
}

addExpireAt(DateGlobal::SECONDS_IN_A_DAY);

⬆ 返回顶部

使用解释型变量

Bad:

$address = 'One Infinite Loop, Cupertino 95014';
$cityZipCodeRegex = '/^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/';
preg_match($cityZipCodeRegex, $address, $matches);
saveCityZipCode($matches[1], $matches[2]);

Good:

$address = 'One Infinite Loop, Cupertino 95014';
$cityZipCodeRegex = '/^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/';
preg_match($cityZipCodeRegex, $address, $matches);
list(, $city, $zipCode) = $matchers;
saveCityZipCode($city, $zipCode);

⬆ 返回顶部

避免心理映射

明确比隐性好。

Bad:

$l = ['Austin', 'New York', 'San Francisco'];
foreach($i=0; $i<count($l); $i++) {
  oStuff();
  doSomeOtherStuff();
  // ...
  // ...
  // ...
  // 等等`$l` 又代表什么?
  dispatch($l);
}

Good:

$locations = ['Austin', 'New York', 'San Francisco'];
foreach($i=0; $i<count($locations); $i++) {
  $location = $locations[$i];
  
  doStuff();
  doSomeOtherStuff();
  // ...
  // ...
  // ...
  dispatch($location);
});

⬆ 返回顶部

不要添加不必要上下文

如果你的class/object 名能告诉你什么,不要把它重复在你变量名里。

Bad:

$car = [
  'carMake'  => 'Honda',
  'carModel' => 'Accord',
  'carColor' => 'Blue',
];

function paintCar(&$car) {
  $car['carColor'] = 'Red';
}

Good:

$car = [
  'make'  => 'Honda',
  'model' => 'Accord',
  'color' => 'Blue',
];

function paintCar(&$car) {
  $car['color'] = 'Red';
}

⬆ 返回顶部

###使用参数默认值代替短路或条件语句。 Bad:

function createMicrobrewery($name = null) {
  $breweryName = $name ?: 'Hipster Brew Co.';
  // ...
}

Good:

function createMicrobrewery($breweryName = 'Hipster Brew Co.') {
  // ...
}

⬆ 返回顶部

函数

函数参数最好少于2个

限制函数参数个数极其重要因为它是你函数测试容易点。有超过3个可选参数参数导致一个爆炸式组合增长,你会有成吨独立参数情形要测试。

无参数是理想情况。1个或2个都可以,最好避免3个。再多旧需要加固了。通常如果你的函数有超过两个参数,说明他多做了一些事。 在参数少的情况里,大多数时候一个高级别对象(数组)作为参数就足够应付。

Bad:

function createMenu($title, $body, $buttonText, $cancellable) {
  // ...
}

Good:

class menuConfig() {
  public $title;
  public $body;
  public $buttonText;
  public $cancellable = false;
}

$config = new MenuConfig();
$config->title = 'Foo';
$config->body = 'Bar';
$config->buttonText = 'Baz';
$config->cancellable = true;

function createMenu(MenuConfig $config) {
  // ...
}

⬆ 返回顶部

函数应该只做一件事

这是迄今为止软件工程里最重要的一个规则。当函数做超过一件事的时候,他们就难于实现、测试和理解。当你隔离函数只剩一个功能时,他们就容易被重构,然后你的代码读起来就更清晰。如果你光遵循这条规则,你就领先于大多数开发者了。

Bad:

function emailClients($clients) {
  foreach ($clients as $client) {
    $clientRecord = $db->find($client);
    if($clientRecord->isActive()) {
       email($client);
    }
  }
}

Good:

function emailClients($clients) {
  $activeClients = activeClients($clients);
  array_walk($activeClients, 'email');
}

function activeClients($clients) {
  return array_filter($clients, 'isClientActive');
}

function isClientActive($client) {
  $clientRecord = $db->find($client);
  return $clientRecord->isActive();
}

⬆ 返回顶部

函数名应当描述他们所做的事

Bad:

function addToDate($date, $month) {
  // ...
}

$date = new \DateTime();

// It's hard to to tell from the function name what is added
addToDate($date, 1);

Good:

function addMonthToDate($month, $date) {
  // ...
}

$date = new \DateTime();
addMonthToDate(1, $date);

⬆ 返回顶部

函数应当只为一层抽象,当你超过一层抽象时,函数正在做多件事。拆分功能易达到可重用性和易用性。.

Bad:

function parseBetterJSAlternative($code) {
  $regexes = [
    // ...
  ];

  $statements = split(' ', $code);
  $tokens = [];
  foreach($regexes as $regex) {
    foreach($statements as $statement) {
      // ...
    }
  }
  
  $ast = [];
  foreach($tokens as $token) {
     // lex...
  }

  foreach($ast as $node) {
   // parse...
  }
}

Good:

function tokenize($code) {
  $regexes = [
    // ...
  ];

  $statements = split(' ', $code);
  $tokens = [];
  foreach($regexes as $regex) {
    foreach($statements as $statement) {
      $tokens[] = /* ... */;
    });
  });

  return tokens;
}

function lexer($tokens) {
  $ast = [];
  foreach($tokens as $token) {
    $ast[] = /* ... */;
  });

  return ast;
}

function parseBetterJSAlternative($code) {
  $tokens = tokenize($code);
  $ast = lexer($tokens);
  foreach($ast as $node) {
    // parse...
  });
}

⬆ 返回顶部

删除重复的代码

尽你最大的努力来避免重复的代码。重复代码不好,因为它意味着如果你修改一些逻辑,那就有不止一处地方要同步修改了。

想象一下如果你经营着一家餐厅并跟踪它的库存: 你全部的西红柿、洋葱、大蒜、香料等。如果你保留有多个列表,当你服务一个有着西红柿的菜,那么所有记录都得更新。如果你只有一个列表,那么只需要修改一个地方!

经常你容忍重复代码,因为你有两个或更多有共同部分但是少许差异的东西强制你用两个或更多独立的函数来做相同的事。移除重复代码意味着创造一个处理这组不同事物的一个抽象,只需要一个函数/模块/类。

抽象正确非常重要,这也是为什么你应当遵循SOLID原则(奠定Class基础的原则)。坏的抽象可能比重复代码还要糟,因为要小心。在这个前提下,如果你可以抽象好,那就开始做把!不要重复你自己,否则任何你想改变一件事的时候你都发现在即在更新维护多处。

Bad:

function showDeveloperList($developers) {
  foreach($developers as $developer) {
    $expectedSalary = $developer->calculateExpectedSalary();
    $experience = $developer->getExperience();
    $githubLink = $developer->getGithubLink();
    $data = [
      $expectedSalary,
      $experience,
      $githubLink
    ];

    render($data);
  }
}

function showManagerList($managers) {
  foreach($managers as $manager) {
    $expectedSalary = $manager->calculateExpectedSalary();
    $experience = $manager->getExperience();
    $githubLink = $manager->getGithubLink();
    $data = [
      $expectedSalary,
      $experience,
      $githubLink
    ];

    render($data);
  }
}

Good:

function showList($employees) {
  foreach($employees as $employe) {
    $expectedSalary = $employe->calculateExpectedSalary();
    $experience = $employe->getExperience();
    $githubLink = $employe->getGithubLink();
    $data = [
      $expectedSalary,
      $experience,
      $githubLink
    ];

    render($data);
  }
}

⬆ 返回顶部

通过对象赋值设置默认值

Bad:

$menuConfig = [
  'title'       => null,
  'body'        => 'Bar',
  'buttonText'  => null,
  'cancellable' => true,
];

function createMenu(&$config) {
  $config['title']       = $config['title'] ?: 'Foo';
  $config['body']        = $config['body'] ?: 'Bar';
  $config['buttonText']  = $config['buttonText'] ?: 'Baz';
  $config['cancellable'] = $config['cancellable'] ?: true;
}

createMenu($menuConfig);

Good:

$menuConfig = [
  'title'       => 'Order',
  // User did not include 'body' key
  'buttonText'  => 'Send',
  'cancellable' => true,
];

function createMenu(&$config) {
  $config = array_merge([
    'title'       => 'Foo',
    'body'        => 'Bar',
    'buttonText'  => 'Baz',
    'cancellable' => true,
  ], $config);

  // config now equals: {title: "Order", body: "Bar", buttonText: "Send", cancellable: true}
  // ...
}

createMenu($menuConfig);

⬆ 返回顶部

不要用标志作为函数的参数,标志告诉你的用户函数做很多事了。函数应当只做一件事。 根据布尔值区别的路径来拆分你的复杂函数。

Bad:

function createFile(name, temp = false) {
  if (temp) {
    touch('./temp/'.$name);
  } else {
    touch($name);
  }
}

Good:

function createFile($name) {
  touch(name);
}

function createTempFile($name) {
  touch('./temp/'.$name);
}

⬆ 返回顶部

避免副作用

一个函数做了比获取一个值然后返回另外一个值或值们会产生副作用如果。副作用可能是写入一个文件,修改某些全局变量或者偶然的把你全部的钱给了陌生人。

现在,你的确需要在一个程序或者场合里要有副作用,像之前的例子,你也许需要写一个文件。你想要做的是把你做这些的地方集中起来。不要用几个函数和类来写入一个特定的文件。用一个服务来做它,一个只有一个。

重点是避免常见陷阱比如对象间共享无结构的数据,使用可以写入任何的可变数据类型,不集中处理副作用发生的地方。如果你做了这些你就会比大多数程序员快乐。

Bad:

// Global variable referenced by following function.
// If we had another function that used this name, now it'd be an array and it could break it.
$name = 'Ryan McDermott';

function splitIntoFirstAndLastName() {
  $name = preg_split('/ /', $name);
}

splitIntoFirstAndLastName();

var_dump($name); // ['Ryan', 'McDermott'];

Good:

$name = 'Ryan McDermott';

function splitIntoFirstAndLastName($name) {
  return preg_split('/ /', $name);
}

$name = 'Ryan McDermott';
$newName = splitIntoFirstAndLastName(name);

var_export($name); // 'Ryan McDermott';
var_export($newName); // ['Ryan', 'McDermott'];

⬆ 返回顶部

不要写全局函数

在大多数语言中污染全局变量是一个坏的实践,因为你可能和其他类库冲突并且你api的用户不明白为什么直到他们获得产品的一个异常。让我们看一个例子:如果你想配置一个数组,你可能会写一个全局函数像config(),但是可能和试着做同样事的其他类库冲突。这就是为什么单例设计模式和简单配置会更好的原因。

Bad:

function config() {
  return  [
    'foo': 'bar',
  ]
};

Good:

class Configuration {
  private static $instance;
  private function __construct($configuration) {/* */}
  public static function getInstance() {
     if(self::$instance === null) {
         self::$instance = new Configuration();
     }
     return self::$instance;
 }
 public function get($key) {/* */}
 public function getAll() {/* */}
}

$singleton = Configuration::getInstance();

⬆ 返回顶部

封装条件语句

Bad:

if ($fsm->state === 'fetching' && is_empty($listNode)) {
  // ...
}

Good:

function shouldShowSpinner($fsm, $listNode) {
  return $fsm->state === 'fetching' && is_empty(listNode);
}

if (shouldShowSpinner($fsmInstance, $listNodeInstance)) {
  // ...
}

⬆ 返回顶部

避免消极条件

Bad:

function isDOMNodeNotPresent($node) {
  // ...
}

if (!isDOMNodeNotPresent($node)) {
  // ...
}

Good:

function isDOMNodePresent($node) {
  // ...
}

if (isDOMNodePresent($node)) {
  // ...
}

⬆ 返回顶部

避免条件声明

这看起来像一个不可能任务。当人们第一次听到这句话是都会这么说。 “没有一个if声明” 答案是你可以使用多态来达到许多case语句里的任务。第二个问题很常见, “那么为什么我要那么做?” 答案是前面我们学过的一个整洁代码原则:一个函数应当只做一件事。当你有类和函数有很多if声明,你自己知道你的函数做了不止一件事。记住,只做一件事。

Bad:

class Airplane {
  // ...
  public function getCruisingAltitude() {
    switch (this.type) {
      case '777':
        return $this->getMaxAltitude() - $this->getPassengerCount();
      case 'Air Force One':
        return $this->getMaxAltitude();
      case 'Cessna':
        return $this->getMaxAltitude() - $this->getFuelExpenditure();
    }
  }
}

Good:

class Airplane {
  // ...
}

class Boeing777 extends Airplane {
  // ...
  public function getCruisingAltitude() {
    return $this->getMaxAltitude() - $this->getPassengerCount();
  }
}

class AirForceOne extends Airplane {
  // ...
  public function getCruisingAltitude() {
    return $this->getMaxAltitude();
  }
}

class Cessna extends Airplane {
  // ...
  public function getCruisingAltitude() {
    return $this->getMaxAltitude() - $this->getFuelExpenditure();
  }
}

⬆ 返回顶部

Avoid 避免类型检查 (part 1)

PHP是弱类型的,这意味着你的函数可以接收任何类型的参数。 有时候你为这自由所痛苦并且在你的函数渐渐尝试类型检查。有很多方法去避免这么做。第一种是考虑API的一致性。

Bad:

function travelToTexas($vehicle) {
  if ($vehicle instanceof Bicycle) {
    $vehicle->peddle($this->currentLocation, new Location('texas'));
  } else if ($vehicle instanceof Car) {
    $vehicle->drive($this->currentLocation, new Location('texas'));
  }
}

Good:

function travelToTexas($vehicle) {
  $vehicle->move($this->currentLocation, new Location('texas'));
}

⬆ 返回顶部

避免类型检查 (part 2)

如果你正使用基本原始值比如字符串、整形和数组,你不能用多态,你仍然感觉需要类型检测,你应当考虑类型声明或者严格模式。 这给你了基于标准PHP语法的静态类型。 手动检查类型的问题是做好了需要好多的废话,好像为了安全就可以不顾损失可读性。保持你的PHP 整洁,写好测试,做好代码回顾。做不到就用PHP严格类型声明和严格模式来确保安全。

Bad:

function combine($val1, $val2) {
  if (is_numeric($val1) && is_numeric(val2)) {
    return val1 + val2;
  }

  throw new \Exception('Must be of type Number');
}

Good:

function combine(int $val1, int $val2) {
  return $val1 + $val2;
}

⬆ 返回顶部

移除僵尸代码

僵尸代码和重复代码一样坏。没有理由保留在你的代码库中。如果从来被调用过,见鬼去!在你的版本库里是如果你仍然需要他的话,因此这么做很安全。

Bad:

function oldRequestModule($url) {
  // ...
}

function newRequestModule($url) {
  // ...
}

$req = new newRequestModule();
inventoryTracker('apples', $req, 'www.inventory-awesome.io');

Good:

function newRequestModule($url) {
  // ...
}

$req = new newRequestModule();
inventoryTracker('apples', $req, 'www.inventory-awesome.io');

⬆ 返回顶部

##有问题反馈 在使用中有任何问题,欢迎反馈给我,可以用以下联系方式跟我交流

[转载]FTP连接报错530 Permission denied解决方法

虚拟机装好RedHat后,准备使用filezilla连接,输入IP地址,root用户,密码,快速连接,报错:

530 Permission denied。

故障排除:

1.首先检查系统是否开启了vsftp服务,如果没有开启,先开启该服务。

2.查看配置

vsftpd的配置,配置文件中限定了vsftpd用户连接控制配置。
vsftpd.ftpusers:位于/etc/vsftpd目录下。它指定了哪些用户账户不能访问FTP服务器,例如root等。
vsftpd.user_list:位于/etc/vsftpd目录下。该文件里的用户账户在默认情况下也不能访问FTP服务器,仅当vsftpd .conf配置文件里启用userlist_enable=NO选项时才允许访问。
vsftpd.conf:位于/etc/vsftpd目录下。来自定义用户登录控制、用户权限控制、超时设置、服务器功能选项、服务器性能选项、服务器响应消息等FTP服务器的配置。

3.配置修改完成后,执行service vsftpd restart重启vsftpd服务。(注:如果为centos7系统,重启服务命令为:/bin/systemctl start vsftpd.service)

[转载]php使用Header函数,PHP_AUTH_PW和PHP_AUTH_USER做用户验证

[这篇文章主要介绍了php使用Header函数,PHP_AUTH_PW和PHP_AUTH_USER做用户验证的方法,结合实例形式分析了PHP使用Header函数调用登录验证及PHP_AUTH_PW和PHP_AUTH_USER进行验证处理的相关技巧,需要的朋友可以参考下]

本文实例讲述了php使用Header函数,PHP_AUTH_PW和PHP_AUTH_USER做用户验证的方法。分享给大家供大家参考,具体如下:

在php中,可以使用Header函数做一些有趣的事情,用户验证就是其中一个很有意思的功能。具体用法:

Header("WWW-Authenticate: Basic realm="USER LOGIN"");
Header("HTTP/1.0 401 Unauthorized");

在页首设计这两个Header函数,页面在载入前会出现一个登录框,要求输入用户名和密码。习惯了在页面登录的我们,是否觉得这样的登录很原始,又很新奇呢?

为了获取从这个对话框中传来的用户名和密码,需要用到php提供的两个特殊变量$PHP_AUTH_USER和$PHP_AUTH_PW,要这样使用这两个特殊变量好像需要在php.ini中设置相关的选项,不然就只能像下面这样引用:

$_SERVER['PHP_AUTH_USER']
$_SERVER['PHP_AUTH_PW']

获取到用户提交上来的用户名和密码之后,要怎样处理逻辑就跟我们一般的程序处理没有什么区别了。下面提供两个例程供参考:

<?php
if(!isset($PHP_AUTH_USER)) {
Header("WWW-authenticate: basic realm="XXX"");
Header("HTTP/1.0 401 Unauthorized");
$title="Login Instructions";
?>
<blockquote>
In order to enter this section of the web site, you must be an XXX
subscriber. If you are a subscriber and you are having trouble logging
in,
please contact <a href="mailto:support@xxx.com">support@xxx.com</a>.
</blockquote>
<?php
exit;
} else {
mysql_pconnect("localhost","nobody","") or die("Unable to connect to SQL server");
mysql_select_db("xxx") or die("Unable to select database");
$user_id=strtolower($PHP_AUTH_USER);
$password=$PHP_AUTH_PW;
$query = mysql_query("select * from users where user_id='$user_id' and password='$password'");
if(!mysql_num_rows($query)) {
Header("WWW-authenticate: basic realm="XXX"");
Header("HTTP/1.0 401 Unauthorized");
$title="Login Instructions";
?>
<blockquote>
In order to enter this section of the web site, you must be an XXX
subscriber. If you are a subscriber and you are having trouble
logging in,
please contact <a href="mailto:support@xxx.com">support@xxx.com</a>.
</blockquote>
<?php
exit;
}
$name=mysql_result($query,0,"name");
$email=mysql_result($query,0,"email");
mysql_free_result($query);
}
?>

另外一个参考的例程:

<?php
//assume user is not authenticated
$auth = false;
$user = $_SERVER['PHP_AUTH_USER'];
$pass = $_SERVER['PHP_AUTH_PW'];
if ( isset($user) && isset($pass) )
{
//connect to db
include 'db_connect.php';
//SQL query to find if this entered username/password is in the db
$sql = "SELECT * FROM healthed_workshop_admin WHERE
user = '$PHP_AUTH_USER' AND
pass = '$PHP_AUTH_PW'";
//put the SQL command and SQL instructions into variable
$result = mysql_query($sql) or die('Unable to connect.');
//get number or rows in command; if more than 0, row is found
$num_matches = mysql_num_rows($result);
if ($num_matches !=0)
{
//matching row found authenticates user
$auth = true;
}
}
if (!$auth)
{
header('WWW-Authenticate: Basic realm="Health Ed Presentation Admin"');
header('HTTP/1.0 401 Unauthorized');
echo 'You must enter a valid username & password.';
exit;
}
else
{
echo 'Success!';
}
?>

扩展:

$_SERVER[‘PHP_AUTH_USER’]

       PHP 的 HTTP 认证机制仅在 PHP 以 Apache 模块方式运行时才有效,因此该功能不适用于 CGI 版本。在 Apache 模块的 PHP 脚本中,可以用 header() 函数来向客户端浏览器发送&ldquo;Authentication Required&rdquo;信息,使其弹出一个用户名/密码输入窗口。当用户输入用户名和密码后,包含有 URL 的 PHP 脚本将会再次和预定义变量 PHP_AUTH_USER、PHP_AUTH_PW 和 AUTH_TYPE 一起被调用,这三个变量分别被设定为用户名,密码和认证类型。预定义变量保存在 $_SERVER 或者 $HTTP_SERVER_VARS 数组中。系统仅支持“基本的”认证

 

复制代码
<?php
$authorization = false;
if($_SERVER['PHP_AUTH_USER'] == "admin" && $_SERVER['PHP_AUTH_PW'] == "admin888"){
    echo "login";
    $authorization = true;
    exit;
}
if(!$authorization){
    header("WWW-Authenticate:Basic realm='Private'");
    header('HTTP/1.0 401 Unauthorized');
    print "You are unauthorized to enter this area.";
}
?>

基于Python爬取转转30W二手交易数据

*爬取结果

1.获取二手商品分类Link

import requests
from bs4 import BeautifulSoup
import pymongo
client = pymongo.MongoClient('localhost',27017)
db_project = client['58project']
cateLinks = db_project['cateLinks']

def getCateLink():
    baseUrl = 'http://sz.58.com/sale.shtml'
    wbData = requests.get(baseUrl)
    soup = BeautifulSoup(wbData.text,'lxml')
    links = soup.select('ul.ym-submnu > li > b > a')
    return list(set('http://sz.58.com{}'.format(link.get('href')) for link in links))

links = getCateLink()
for link in links:
    cateLinks.insert_one({'cateLinks':link})

2.获取二手商品Link

import requests
from bs4 import BeautifulSoup
import time
import random
import pymongo
import re

client = pymongo.MongoClient('localhost', 27017)
db_project = client['58project']
goodsLinksSheet = db_project['new_goodsLinks']
errorNote = db_project['errorNote']


def getGoodsLink(cateLink, page):
    url = '{}pn{}/'.format(cateLink, str(page))
    wbData = requests.get(url)
    if not wbData:
        errorNote.insert_one({'cateLink':cateLink,'page':page})
        return False
    soup = BeautifulSoup(wbData.text, 'lxml')
    if not soup.find('div', 'noinfotishi'):
        time.sleep(random.randrange(2, 4))
        for link in soup.find_all('a', class_='t', href=re.compile('zhuanzhuan.58')):
            goodsLink = link.get('href').split('?')[0]
            goodsLinksSheet.insert_one({'goodsLink': goodsLink})
            print(cateLink,'|',page,'|', goodsLink)
    else:
        return False

3.获取二手商品详情信息并逐条插入MongoDB

import requests
from bs4 import BeautifulSoup
import time
from getMongoData import getData
import pymongo
import random
import re

client = pymongo.MongoClient('localhost', 27017)
projectDb = client['58project']
goodsInfo = projectDb['goodsInfo']
fiter = projectDb['fiter']


def getGoodsinfo(goodsLink):
    if goodsInfo.find({'goodsLink': goodsLink}).count() == 0:
        time.sleep(random.randrange(3, 5))
        wbData = requests.get(goodsLink)
        if wbData.status_code == 200:
            soup = BeautifulSoup(wbData.text, 'lxml')
            if soup.find('p',class_ = 'personal_name'):
                name = soup.find('p', class_='personal_name').get_text() if soup.find('p', class_='personal_name') else None
                join58Age = re.sub('\D', '', soup.find('p', class_='personal_chengjiu').get_text()) if soup.find('p',class_='personal_chengjiu') else 0
                orderNum = soup.find('span', class_='numdeal').get_text() if soup.find('span', class_='numdeal') else 0
                title = soup.find('h1', class_='info_titile').get_text() if soup.find('h1', class_='info_titile') else None
                viewTimes = re.sub('\D', '', soup.find('span', class_='look_time').get_text()) if soup.find('span', class_='look_time') else 0
                price = soup.select('.price_now  i')[0].get_text() if soup.select('.price_now  i') else 0
                address = soup.select('.palce_li  i')[0].get_text().split('-') if soup.select('.palce_li  i') else None
                bodyPic = list(map(lambda x: x.get('src'), soup.select('div.boby_pic > img'))) if soup.select(
                    'div.boby_pic > img') else None
                describe = soup.select('.baby_kuang p')[0].get_text() if soup.select('.baby_kuang p') else None
                headImgLink = soup.select('.personal_touxiang img')[0].get('src') if soup.select('.personal_touxiang img') else None
                bodyPic = '|'.join(bodyPic) if bodyPic != None else None
                data = {
                    'name': name,
                    'join58Age': int(join58Age) if join58Age != '' else 0,
                    'orderNum': int(orderNum),
                    'title': title,
                    'viewTimes': int(viewTimes) if viewTimes != '' else 0,
                    'price': int(price) if price.isdigit() else 0,
                    'address': address,
                    'describe': describe,
                    'headImgLink': headImgLink,
                    'bodyPic': bodyPic,
                    'goodsLink': goodsLink
                }
                goodsInfo.insert_one(data)
                fiter.insert_one({'url': goodsLink})
                print(data, '\n')
    else:
        print(goodsLink)

def deleteData():
    res = goodsInfo.delete_many({'name': ''})
    print(res)

def getLineNum():
    # goodsLink = getData('new_goodsLinks')
    # res = set(map(lambda x:x['goodsLink'],goodsLink))
    res = goodsInfo.distinct('goodsLink')
    print(len(res))

def repeat():
    links = goodsInfo.find()
    tmp = set()
    for link in links:
        if link['goodsLink'] not in tmp:
            tmp.add(link['goodsLink'])
        else:
            # goodsInfo.delete_one({'goodsLink':link['goodsLink']})
            print(link)

4.获取MongoDB已存储的数据

import pymongo

def getData(sheetName):
    client = pymongo.MongoClient('localhost',27017)
    projectDb = client['58project']
    sheetObj = projectDb[sheetName]
    return sheetObj.find()

5.执行入口主文件,多线程异步执行爬去任务

from getGoodsLinks import getGoodsLink
from multiprocessing import Pool
from getMongoData import getData
from getGoodsInfo import getGoodsinfo
from getGoodsInfo import deleteData
from getGoodsInfo import getLineNum
from getGoodsInfo import repeat


def startSpider(cateLink):
    for page in range(1, 101):
        if not getGoodsLink(cateLink, str(page)):
            continue


# if __name__ == '__main__':
#     pool = Pool(processes = 8)
#     links = getCateLink()
#     pool.map(startSpider,links)
# 
# if __name__ == '__main__':
#     goodsLink = getData('new_goodsLinks')
#     urlPond = list(map(lambda x:x['goodsLink'],goodsLink))
#     pool = Pool(processes=4)
#     pool.map(getGoodsinfo, urlPond)
# 
# if __name__ == '__main__':
#     getLineNum()