GeekGhc

GeekGhc 查看完整档案

南京编辑南京金陵科技学院  |  软件工程 编辑字节跳动  |  后端开发 编辑 blog.geekghc.com 编辑
编辑

Never underestimate your power to change yourself

Github:https://github.com/GeekGhc

个人动态

GeekGhc 赞了文章 · 2019-07-30

Laravel 服务提供者指南

这是一篇翻译文章,译文首发于 Laravel 服务提供者指南,转载请注明出处。

如果你使用过 Laravel 框架的话,那么,你不可能没听说过服务容器和服务提供者。事实上,它们是 Lavavel 框架核心,它们完成 Larvel 应用中服务启动的艰巨任务。

在这篇文章中,我们将简单介绍「服务容器」,同时还会深入讲解服务提供者。本教程还将演示如何在 Laravel 中创建一个自定义的服务提供者。另外,如果你需要在 Laravel 中成功使用服务容器,还需要注册它。那么,让我们开始吧。

实现一个自定义的服务提供者,需要实现两个非常重要的方法:bootregister 方法。关于这两个方法将在教程最后一个小节讨论。

在学习服务提供者之前,简单介绍一下服务容器,服务容器会在服务提供者中被经常使用。

理解服务容器和服务提供者

什么是服务容器

简而言之,Laravel 服务容器 是一个用于存储绑定组件的盒子,它还会为应用提供所需的服务。

Laravel 文档中描述如下:

Laravel 服务容器是用于管理类的依赖和执行依赖注入的工具 - Laravel 文档

这样,当我们需要注入一个内置的组件或服务时,可以在构造函数或方法中使用类型提示功能注入,然后在使用时从服务容器中自动解析出所需实例及其依赖!是不是很酷?这个功能可以让我们从手动管理组件中解脱出来,从而降低系统耦合度。

让我们看一个简单实例来加深理解。

<?php

Class SomeClass
{
    public function __construct(FooBar $foobarObject)
    {
        // use $foobarObject object
    }
}

如你所见,SomeClass 需要使用 FooBar 实例。换句话说它需要依赖其它组件。Laravel 实现自动注入需要从服务容器中查找并执行注入适当的依赖。

如果你希望了解 Laravel 是如何知道需要将哪个组件或服务绑定到服务容器中的,答案是通过服务提供者实现的。服务提供者完成将组件绑定到服务容器的工作。在服务提供者内部,这个工作被称之为服务容器绑定,绑定处理由服务提供者完成。

服务提供者实现了服务绑定,绑定处理则由 register 方法完成。

同时,这又会引入一个新的问题:Laravel 是如何知道有哪些服务提供者的呢?这个我们貌似还没有讨论到吧?我到时看到,之前有说 Laravel 会自动的去查找到服务!朋友,你的问题太多了:Laravel 只是一个框架,它不是一个超级英雄,不是么?我们当然需要去明确的告知 Laravel 框架我们有哪些服务提供者。

让我们来瞧瞧 config/app.php 配置文件。你会找到一个用于 Laravel 应用启动过程中被载入的服务提供者配置列表。

'providers' => [
  
        /*
         * Laravel Framework Service Providers...
         */
        Illuminate\Auth\AuthServiceProvider::class,
        Illuminate\Broadcasting\BroadcastServiceProvider::class,
        Illuminate\Bus\BusServiceProvider::class,
        Illuminate\Cache\CacheServiceProvider::class,
        Illuminate\Foundation\Providers\ConsoleSupportServiceProvider::class,
        Illuminate\Cookie\CookieServiceProvider::class,
        Illuminate\Database\DatabaseServiceProvider::class,
        Illuminate\Encryption\EncryptionServiceProvider::class,
        Illuminate\Filesystem\FilesystemServiceProvider::class,
        Illuminate\Foundation\Providers\FoundationServiceProvider::class,
        Illuminate\Hashing\HashServiceProvider::class,
        Illuminate\Mail\MailServiceProvider::class,
        Illuminate\Notifications\NotificationServiceProvider::class,
        Illuminate\Pagination\PaginationServiceProvider::class,
        Illuminate\Pipeline\PipelineServiceProvider::class,
        Illuminate\Queue\QueueServiceProvider::class,
        Illuminate\Redis\RedisServiceProvider::class,
        Illuminate\Auth\Passwords\PasswordResetServiceProvider::class,
        Illuminate\Session\SessionServiceProvider::class,
        Illuminate\Translation\TranslationServiceProvider::class,
        Illuminate\Validation\ValidationServiceProvider::class,
        Illuminate\View\ViewServiceProvider::class,
  
        /*
         * Package Service Providers...
         */
        Laravel\Tinker\TinkerServiceProvider::class,
  
        /*
         * Application Service Providers...
         */
        App\Providers\AppServiceProvider::class,
        App\Providers\AuthServiceProvider::class,
        // App\Providers\BroadcastServiceProvider::class,
        App\Providers\EventServiceProvider::class,
        App\Providers\RouteServiceProvider::class,
],

以上就是有关服务容器的基本概念。下一节,我们将焦点聚集到服务提供者这个核心主题上!

什么是服务提供者

如果说服务容器是提供绑定和依赖注入的的工具,那么 服务提供者 则是实现绑定的工具。

让我们先来看一个内容提供的服务提供者服务来理解它的运行原理。打开 vender/laravel/framework/src/Illuminate/Cache/CacheServiceProvider.php 文件。

public function register()
{
    $this->app->singleton('cache', function ($app) {
        return new CacheManager($app);
    });
  
    $this->app->singleton('cache.store', function ($app) {
        return $app['cache']->driver();
    });
  
    $this->app->singleton('memcached.connector', function () {
        return new MemcachedConnector;
    });
}

这里我们需要将重点集中在 register 方法中,这个方法用于绑定服务到服务容器。如你所见,这里一共执行了三个服务的绑定处理:cachecache.storememcached.connector

然后,当我们需要在 Laravel 中使用 cache 服务是,服务容器会解析出 CacheManager 实例并返回。也就是说我们仅仅是提供了一个可以从 $this->app 访问的对应关系表。

通过服务提供者绑定服务是 Laravel 服务容器绑定服务的正确打开方式。同时通过服务提供者的 register 方法,还有利于理解 Laravel 服务容器是如何管理所有的服务的。我们之前提到过,通过从 config/app.php 配置文件中读取服务提供者配置列表,从将所有服务注册服务容器中。

以上,就是服务提供者和它的故事。下一节,我们会学习如何创建一个服务提供者来实现将自己的服务注册到 Laravel 服务容器。

自定义服务提供者

Laravel 已经内置了一个用于创建服务提供者的 artisan 命令来简化创建流程。进入命令行模式后执行下面命令来创建服务提供者。

php artisan make:provider EnvatoCustomServiceProvider

运行后会在 app/Providers 目录下创建 EnvatoCustomServiceProvider.php 文件。打开该文件看下它的源码。

<?php
namespace App\Providers;
  
use Illuminate\Support\ServiceProvider;
  
class EnvatoCustomServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap the application services.
     *
     * @return void
     */
    public function boot()
    {
        //
    }
  
    /**
     * Register the application services.
     *
     * @return void
     */
    public function register()
    {
        //
    }
}

之前我们有提到服务提供者有两个重要方法:bootregister 方法,在实现自定义服务提供者时大部分都是在处理这两个方法。

register 方法用于执行服务绑定处理。另外在 boot 方法中可以使用所有已绑定的服务。在这个教程的最后一节我们将学习更多有关这两个方法的细节,但在这里我们会先了解些这两个方法的使用示例加深理解。

注册自定义服务提供者

前面我们创建了一个自定义的服务提供者。接下来需要让 Laravel 知道如何让这个服务提供者同其它服务提供者一样在应用启动时被加载到 Laravel 中。

为了完成注册服务提供者的功能,仅需要将类名加入到 config/app.php 配置文件的 providers 节点。

'providers' => [
  
        /*
         * Laravel Framework Service Providers...
         */
        Illuminate\Auth\AuthServiceProvider::class,
        Illuminate\Broadcasting\BroadcastServiceProvider::class,
        Illuminate\Bus\BusServiceProvider::class,
        Illuminate\Cache\CacheServiceProvider::class,
        Illuminate\Foundation\Providers\ConsoleSupportServiceProvider::class,
        Illuminate\Cookie\CookieServiceProvider::class,
        Illuminate\Database\DatabaseServiceProvider::class,
        Illuminate\Encryption\EncryptionServiceProvider::class,
        Illuminate\Filesystem\FilesystemServiceProvider::class,
        Illuminate\Foundation\Providers\FoundationServiceProvider::class,
        Illuminate\Hashing\HashServiceProvider::class,
        Illuminate\Mail\MailServiceProvider::class,
        Illuminate\Notifications\NotificationServiceProvider::class,
        Illuminate\Pagination\PaginationServiceProvider::class,
        Illuminate\Pipeline\PipelineServiceProvider::class,
        Illuminate\Queue\QueueServiceProvider::class,
        Illuminate\Redis\RedisServiceProvider::class,
        Illuminate\Auth\Passwords\PasswordResetServiceProvider::class,
        Illuminate\Session\SessionServiceProvider::class,
        Illuminate\Translation\TranslationServiceProvider::class,
        Illuminate\Validation\ValidationServiceProvider::class,
        Illuminate\View\ViewServiceProvider::class,
  
        /*
         * Package Service Providers...
         */
        Laravel\Tinker\TinkerServiceProvider::class,
  
        /*
         * Application Service Providers...
         */
        App\Providers\AppServiceProvider::class,
        App\Providers\AuthServiceProvider::class,
        // App\Providers\BroadcastServiceProvider::class,
        App\Providers\EventServiceProvider::class,
        App\Providers\RouteServiceProvider::class,
        App\Providers\EnvatoCustomServiceProvider::class,
],

就是如此简单,现在你已经将自定义服务提供者注册到了 Laravel 中。只不过现在这个服务提供者还几乎什么都没有处理。下一节,我们将以实例演示如何使用 registerboot 方法。

深入讲解 register 和 boot 方法

起先,我们来深入研究 register 方法加深你对这个方法的理解。打开之前创建的 app/Providers/EnvatoCustomServiceProvider.php 文件,加入如下代码。

<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use App\Library\Services\DemoOne;

class EnvatoCustomServiceProvider extends ServiceProvider
{
    public function boot()
    {
    }

    public function register()
    {
        $this->app->bind('App\Library\Services\DemoOne', function ($app) {
            return new DemoOne();
        });
    }
}

这里我们做了两个处理:

  • 引入需要使用的 AppLibraryServicesDemoOne 服务。DemoOne 类现在还没有创建,但之后会创建这个类。
  • register 方法中,我们使用服务容器的 bind 方法将服务绑定到容器。这样,当需要使用 AppLibraryServicesDemoOne 服务而被解析时,就回调用闭包方法,创建实例并返回 AppLibraryServicesDemoOne 对象。

现在创建 app/Library/Services/DemoOne.php 文件。

<?php
namespace App\Library\Services;
  
class DemoOne
{
    public function doSomethingUseful()
    {
      return 'Output from DemoOne';
    }
}

然后,在控制器的构造函数中注入依赖。

<?php
namespace App\Http\Controllers;
  
use App\Http\Controllers\Controller;
use App\Library\Services\DemoOne;
  
class TestController extends Controller
{
    public function index(DemoOne $customServiceInstance)
    {
        echo $customServiceInstance->doSomethingUseful();
    }
}

以上便是一个使用绑定的简单方法。事实上,对于这个示例其实并不需要创建一个服务提供者,并实现 register 方法,因为 Laravel 还可以通过 PHP 的方式功能自动解析。

Laravel 文档中对此有一个说明:

如果我们的依赖无需任何接口,则无需将类绑定到容器。容器此时不需要了解创建对象的具体细节,而可以通过反射功能实现自动注入。

换句话说,如果我们需要绑定的服务依赖于其它接口,创建服务提供者则很有必要。接着来看一个实例以加深理解。

首先,创建一个简单的接口 app/Library/Services/Contracts/CustomServiceInterface.php

<?php
// app/Library/Services/Contracts/CustomServiceInterface.php
namespace App\Library\Services\Contracts;
  
Interface CustomServiceInterface
{
    public function doSomethingUseful();
}

然后,创建两个基于此接口的具体实现。或者说,创建两个继承此接口的实现类。

一个是定义在 app/Library/Services/DemoOne.php 文件中的 DemoOne 类。

<?php
// app/Library/Services/DemoOne.php
namespace App\Library\Services;
  
use App\Library\Services\Contracts\CustomServiceInterface;
  
class DemoOne implements CustomServiceInterface
{
    public function doSomethingUseful()
    {
      return 'Output from DemoOne';
    }
}

类似的,还有 app/Library/Services/DemoTwo.php

<?php
// app/Library/Services/DemoTwo.php
namespace App\Library\Services;
  
use App\Library\Services\Contracts\CustomServiceInterface;
  
class DemoTwo implements CustomServiceInterface
{
    public function doSomethingUseful()
    {
      return 'Output from DemoTwo';
    }
}

现在,将绑定具体类名修改为绑定接口。打开 EnvatoCustomServiceProvider.php 文件并改成如何代码。

<?php
namespace App\Providers;
  
use Illuminate\Support\ServiceProvider;
use App\Library\Services\DemoOne;
  
class EnvatoCustomServiceProvider extends ServiceProvider
{
    public function boot()
    {
    }
  
    public function register()
    {
        $this->app->bind('App\Library\Services\Contracts\CustomServiceInterface', function ($app) {
          return new DemoOne();
        });
    }
}

这里,我们将 DemoOne 实现类绑定到 AppLibraryServicesContractsCustomServiceInterface 接口。后续,所有依赖 AppLibraryServicesContractsCustomServiceInterface 接口的功能都被解析成 AppLibraryServicesDemoOne 对象。 这个示例是不是更有实际意义呢?

当然,我们还需要调整下控制器中的代码。

<?php
namespace App\Http\Controllers;
  
use App\Http\Controllers\Controller;
use App\Library\Services\Contracts\CustomServiceInterface;
  
class TestController extends Controller
{
    public function index(CustomServiceInterface $customServiceInstance)
    {
        echo $customServiceInstance->doSomethingUseful();
    }
}

或许你已经猜到 $customServiceInstance 对象是 AppLibraryServicesDemoOne 类的实例!这种方案的优势在于可以很容易的替换掉 DemoOne 这个实现。

假如你想使用 DemoTwo 替换掉 DemoOne 服务。此时,仅需简单的调整下服务提供者中的代码 EnvatoCustomServiceProvider.php

将:

use App\Library\Services\DemoOne;

替换成:

use App\Library\Services\DemoTwo;

然后替换:

return new DemoOne();

到:

return new DemoTwo();

使用同样的手法甚至可以将自定义的实现替换掉任何核心服务中的依赖。不仅如此,除了 bind 方法;Laravel 服务容器还提供多种绑定方法。可以查看 Laravel 服务容器 文档了解更多。

下一个主题是可以扩展 Laravel 核心服务的 boot 方法。在这个方法中,你可以获取所有通过服务提供者注册到容器中的服务。通常,你会在这个方法中注册某些功能完成后需要触发其它操作的事件监听器。

依照惯例看几个示例先。

创建一个用于 Laravel 校验的自定义表单验证器。

public function boot()
{
    Validator::extend('my_custom_validator', function ($attribute, $value, $parameters, $validator) {
        // validation logic goes here...
    });
}

也许你想创建一个 view composer。在 boot 方法中创建是个不错的选择。

public function boot()
{
    View::composer(
        'demo', 'App\Http\ViewComposers\DemoComposer'
    );
}

当然在这里需要率先导入 IlluminateSupportFacadesView

有时,我们还需要创建一些共享数据。

public function boot()
{
    View::share('key', 'value');
}

甚至可以显示的创建模型绑定。

public function boot()
{
    parent::boot();
  
    Route::model('user', App\User::class);
}

这些示例演示了 boot 方法的一些用法。只有更深入的理解,才能掌握它的使用方法!

与此同时,我们需要说再见了。我希望你喜欢本文所讨论的主题。

结论

本文讨论的是服务提供者,这是本文的中心思想,尽管我们是以服务容器作为开篇,因为它是理解服务提供者的重要组成部分。

随后,我们创建了一个自定义服务提供者,并且在本文的后半部分中,我们介绍了几个实际的示例。

原文: How to Register & Use Laravel Service Providers

查看原文

赞 8 收藏 6 评论 0

GeekGhc 关注了用户 · 2019-02-20

summerblue @summerblue

刻意练习,每日精进

关注 11014

GeekGhc 关注了标签 · 2018-10-22

golang

Go语言是谷歌2009发布的第二款开源编程语言。Go语言专门针对多处理器系统应用程序的编程进行了优化,使用Go编译的程序可以媲美C或C++代码的速度,而且更加安全、支持并行进程。
Go语言是谷歌推出的一种全新的编程语言,可以在不损失应用程序性能的情况下降低代码的复杂性。谷歌首席软件工程师罗布派克(Rob Pike)说:我们之所以开发Go,是因为过去10多年间软件开发的难度令人沮丧。Go是谷歌2009发布的第二款编程语言。

七牛云存储CEO许式伟出版《Go语言编程
go语言翻译项目 http://code.google.com/p/gola...
《go编程导读》 http://code.google.com/p/ac-m...
golang的官方文档 http://golang.org/doc/docs.html
golang windows上安装 http://code.google.com/p/gomi...

关注 25995

GeekGhc 赞了文章 · 2018-08-29

Hexo博客利用Netlify升级为HTTPS

探索之旅

一开始是准备用Cloudflare的,不过因为Cloudflare需要更改DNS服务器,而我的某个二级域名被捞仔们拿去开发招新网页了,修改DNS服务器会让整个招新网页域名解析错误,所以我就放弃了这个方案。

Cloudflare实际上是个CDN服务商,其中开启SSL有个Flexible选项。开启后实际上当用户访问站点时,访问的内容是从CloudFlare的CDN服务器传输过来的,之间的传输采用的就是HTTPS协议,信息是加密的,而CDN与源服务器(github Page)的传输用的是普通的HTTP协议。

官网也告知说,要使用CloudFlare,需要去域名提供商那里更改DNS服务器,变为它提供的域名服务器地址。喜闻乐见,我改完DNS后,NW的招新网站就炸了,打扰了。
而且CloudFlare的访问速度也很慢,很慢,很慢。

使用tarcert来跟踪IP数据报的访问路径。发现我们的访问路径,从中国到了部署在欧洲的CloudFlare服务器再到达美国的github服务器。好吧,打扰了。

我继续科学搜索,找到了一个很好用的静态资源托管平台Netlify。

什么是Netlify?

它是一个提供静态资源网络托管的综合平台。

简单来说: 它的功能之一就跟我们之前Hexo博客的静态托管平台 Github Page一样, 不过,Netlify可比github Page功能多多了,而且速度也快。两者的对比在netlify官网有介绍

简单来说它可以

  • 托管静态资源
  • 将静态网站部署到CDN上
  • Continuous Deployment 持续部署,当你提交改变到git 仓库,它就会自动运行build command,进行自动部署。
  • 可以添加自定义域名
  • 重头戏:可以启用免费的TLS证书,启用HTTPS
www.netlify.com 官网有更详细的文档内容。

开始前的准备,创建新仓库

之前的那个 yourname.github.io 的仓库继续保留着,可以不用删。

我们新建一个仓库blog-netlify,不要添加README.md还有其他的一些东西,就创建个空仓库就好,不然后续步骤可能会因此报错。

然后我们在我们的博客目录下(.../../hexo)进行git的初始化

git init  

我们需要将hexo生成的静态文件给忽略掉,还有node_modules,还有其他一些文件。

将忽略文件添加到.gitignore文件, 可以使用CLI的方式添加,或者手动创建文件,直接输入需要忽略的文件。

echo "/public" >> .gitignore
echo "/node_modules" >> .gitignore
echo "/.deploy_git" >> .gitignore
echo "/.vscode" >> .gitignore

查看当前的工作目录和暂存区状态,如果还看到一些我们忽略的文件出现,则添加到.gitignore中。

git status

将文件修改添加到暂存区中,并提交实际改动到分支上

git add .

git commit -m "[F]Hello Netlify"

绑定远程仓库,输入刚才我们创建的仓库的地址

git remote add origin <your repo url>  

查看绑定状态

git remote -v

没什么问题就push到远程分支

git push origin master

建立新站点

好了,一个netlify需要用到的仓库就搞定了,然后到netlify官网 用github账户登录

点击页面的新建站点按钮

选择Github,

还有就是下面给netlify提供github的权限,这里需要勾上

选择你的博客仓库.

Build command 填入 Hexo g Publish directory 填入 public

点击部署站点,它就会开始部署,稍等一下可以看到它的log日志,没什么问题的话,会显示成功部署。

接下来你可以通过它提供的二级域名xxxx.netlify.com来进行访问你的站点,你也可以自定义域名,添加自定义域名,先添加不带www的域名作为主域名,它会自动添加一个www.domain.cc重定向到domain.cc
这时它会检查你添加的两个域名,域名服务器来检查是否可以解析你的自定义域名,现在当然是不可以的,需要你去添加解析记录。

通常需要去你的域名提供商那里添加两条解析记录,一条A记录,一条CNAME记录


到这里就可以 通过自定义域名访问了,然后就是使用HTTPS了。

使用HTTPS

Netlify 使用的是 Let’s Encrypt Certificate.的免费证书,我这里因为我自己之前在腾讯云免费申请过证书了,所以这里就使用我自己的证书.
证书:


第一项需要填的是你当前站点的安全证书,就是上图的chenpt.cc.crt

第二项是对应的私钥

第三项是中级证书,就是根证书下来的那个证书,就是上图的root_bundle.crt

打开证书文件你可以看到证书路径如下

使用的证书跟Apache一样,所以Netlify内部可能使用是Apache来配置HTTPS模块的。

输入完毕加载证书。 然后再启用强制HTTPS。
只能使用HTTPS协议的URL来访问站点,使用HTTP协议的URL会自动重定向HTTPS协议的URL,内部实现是返回301状态码 Move permanently(永久性移动)

开始新博客之旅

现在你想发布博客还需要hexo g -d 么?

答案是不用了。

现在是在source文件夹内的_posts文件添加新文章。然后把仓库的改变提交到分支上,再提交到远程分支上,Netlify获取仓库改变,会自动部署。 所以现在是很方便了。

而且netlify的速度比github page快了很多,体验nice!

之前的github page仓库还是留着吧,还有就是需要去把之前对github page的域名解析记录给删掉。然后就拥抱netlify吧.

希望大家都有小绿锁.

查看原文

赞 8 收藏 4 评论 1

GeekGhc 收藏了文章 · 2018-08-08

数据库两个神器【索引和锁】

前言

只有光头才能变强

索引和锁在数据库中可以说是非常重要的知识点了,在面试中也会经常会被问到的。

本文力求简单讲清每个知识点,希望大家看完能有所收获

声明:如果没有说明具体的数据库和存储引擎,默认指的是MySQL中的InnoDB存储引擎

一、索引

在之前,我对索引有以下的认知:

  • 索引可以加快数据库的检索速度
  • 经常进行INSERT/UPDATE/DELETE操作就不要建立索引了,换言之:索引会降低插入、删除、修改等维护任务的速度。
  • 索引需要占物理和数据空间
  • 了解过索引的最左匹配原则
  • 知道索引的分类:聚集索引和非聚集索引
  • Mysql支持Hash索引和B+树索引两种

看起来好像啥都知道,但面试让你说的时候可能就GG了:

  • 使用索引为什么可以加快数据库的检索速度啊?
  • 为什么说索引会降低插入、删除、修改等维护任务的速度。
  • 索引的最左匹配原则指的是什么?
  • Hash索引和B+树索引有什么区别?主流的使用哪一个比较多?InnoDB存储都支持吗?
  • 聚集索引和非聚集索引有什么区别?
  • ........

1.1聊聊索引的基础知识

首先Mysql的基本存储结构是(记录都存在页里边):

  • 各个数据页可以组成一个双向链表
  • 每个数据页中的记录又可以组成一个单向链表

    • 每个数据页都会为存储在它里边儿的记录生成一个页目录,在通过主键查找某条记录的时候可以在页目录中使用二分法快速定位到对应的槽,然后再遍历该槽对应分组中的记录即可快速找到指定的记录
    • 其他列(非主键)作为搜索条件:只能从最小记录开始依次遍历单链表中的每条记录

所以说,如果我们写select * from user where username = 'Java3y'这样没有进行任何优化的sql语句,默认会这样做:

  • 定位到记录所在的页

    • 需要遍历双向链表,找到所在的页
  • 从所在的页内中查找相应的记录

    • 由于不是根据主键查询,只能遍历所在页的单链表了

很明显,在数据量很大的情况下这样查找会很慢

1.2索引提高检索速度

索引做了些什么可以让我们查询加快速度呢?

其实就是将无序的数据变成有序(相对)

要找到id为8的记录简要步骤:

很明显的是:没有用索引我们是需要遍历双向链表来定位对应的页,现在通过“目录”就可以很快地定位到对应的页上了!

其实底层结构就是B+树,B+树作为树的一种实现,能够让我们很快地查找出对应的记录。

参考资料:

  • Mysql索引&version=12020810&nettype=WIFI&lang=zh_CN&fontScale=100&pass_ticket=YHEmqDDX8hHkj5FiSVpQvjYqIMBDHHDS2po4mfJe%2BqIXlqwJI%2Bg7aJUZq0%2BDwGJ0)

1.3索引降低增删改的速度

B+树是平衡树的一种。

平衡树:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。

如果一棵普通的树在极端的情况下,是能退化成链表的(树的优点就不复存在了)

B+树是平衡树的一种,是不会退化成链表的,树的高度都是相对比较低的(基本符合矮矮胖胖(均衡)的结构)【这样一来我们检索的时间复杂度就是O(logn)】!从上一节的图我们也可以看见,建立索引实际上就是建立一颗B+树。

  • B+树是一颗平衡树,如果我们对这颗树增删改的话,那肯定会破坏它的原有结构
  • 要维持平衡树,就必须做额外的工作。正因为这些额外的工作开销,导致索引会降低增删改的速度

B+树删除和修改具体可参考:

1.4哈希索引

除了B+树之外,还有一种常见的是哈希索引。

哈希索引就是采用一定的哈希算法,把键值换算成新的哈希值,检索时不需要类似B+树那样从根节点到叶子节点逐级查找,只需一次哈希算法即可立刻定位到相应的位置,速度非常快

  • 本质上就是把键值换算成新的哈希值,根据这个哈希值来定位

看起来哈希索引很牛逼啊,但其实哈希索引有好几个局限(根据他本质的原理可得):

  • 哈希索引也没办法利用索引完成排序
  • 不支持最左匹配原则
  • 在有大量重复键值情况下,哈希索引的效率也是极低的---->哈希碰撞问题。
  • 不支持范围查询

参考资料:

1.5InnoDB支持哈希索引吗?

主流的还是使用B+树索引比较多,对于哈希索引,InnoDB是自适应哈希索引的(hash索引的创建由InnoDB存储引擎引擎自动优化创建,我们干预不了)!

参考资料:

1.6聚集和非聚集索引

简单概括:

  • 聚集索引就是以主键创建的索引
  • 非聚集索引就是以非主键创建的索引

区别:

  • 聚集索引在叶子节点存储的是表中的数据
  • 非聚集索引在叶子节点存储的是主键和索引列
  • 使用非聚集索引查询出数据时,拿到叶子上的主键再去查到想要查找的数据。(拿到主键再查找这个过程叫做回表)

非聚集索引也叫做二级索引,不用纠结那么多名词,将其等价就行了~

非聚集索引在建立的时候也未必是单列的,可以多个列来创建索引。

  • 此时就涉及到了哪个列会走索引,哪个列不走索引的问题了(最左匹配原则-->后面有说)
  • 创建多个单列(非聚集)索引的时候,会生成多个索引树(所以过多创建索引会占用磁盘空间)

在创建多列索引中也涉及到了一种特殊的索引-->覆盖索引

  • 我们前面知道了,如果不是聚集索引,叶子节点存储的是主键+列值
  • 最终还是要“回表”,也就是要通过主键查找一次。这样就会比较慢
  • 覆盖索引就是把要查询出的列和索引是对应的,不做回表操作!

比如说:

  • 现在我创建了索引(username,age),在查询数据的时候:select username , age from user where username = 'Java3y' and age = 20
  • 很明显地知道,我们上边的查询是走索引的,并且,要查询出的列在叶子节点都存在!所以,就不用回表了~
  • 所以,能使用覆盖索引就尽量使用吧~

1.7索引最左匹配原则

最左匹配原则

  • 索引可以简单如一个列(a),也可以复杂如多个列(a, b, c, d),即联合索引
  • 如果是联合索引,那么key也由多个列组成,同时,索引只能用于查找key是否存在(相等),遇到范围查询(>、<、between、like左匹配)等就不能进一步匹配了,后续退化为线性查找。
  • 因此,列的排列顺序决定了可命中索引的列数

例子:

  • 如有索引(a, b, c, d),查询条件a = 1 and b = 2 and c > 3 and d = 4,则会在每个节点依次命中a、b、c,无法命中d。(c已经是范围查询了,d肯定是排不了序了)

为什么能命中c?

举个简单例子:select * from user where age >30; 如果在age列创建索引,那你说会走索引吗?

1.8=、in自动优化顺序

不需要考虑=、in等的顺序,mysql会自动优化这些条件的顺序,以匹配尽可能多的索引列。

例子:

  • 如有索引(a, b, c, d),查询条件c > 3 and b = 2 and a = 1 and d < 4a = 1 and c > 3 and b = 2 and d < 4等顺序都是可以的,MySQL会自动优化为a = 1 and b = 2 and c > 3 and d < 4,依次命中a、b、c。

1.9索引总结

索引在数据库中是一个非常重要的知识点!上面谈的其实就是索引最基本的东西,要创建出好的索引要顾及到很多的方面:

  • 1,最左前缀匹配原则。这是非常重要、非常重要、非常重要(重要的事情说三遍)的原则,MySQL会一直向右匹配直到遇到范围查询(>,<,BETWEEN,LIKE)就停止匹配。
  • 3,尽量选择区分度高的列作为索引,区分度的公式是 COUNT(DISTINCT col) / COUNT(*)。表示字段不重复的比率,比率越大我们扫描的记录数就越少。
  • 4,索引列不能参与计算,尽量保持列“干净”。比如,FROM_UNIXTIME(create_time) = '2016-06-06' 就不能使用索引,原因很简单,B+树中存储的都是数据表中的字段值,但是进行检索时,需要把所有元素都应用函数才能比较,显然这样的代价太大。所以语句要写成 : create_time = UNIX_TIMESTAMP('2016-06-06')
  • 5,尽可能的扩展索引,不要新建立索引。比如表中已经有了a的索引,现在要加(a,b)的索引,那么只需要修改原来的索引即可。
  • 6,单个多列组合索引和多个单列索引的检索查询效果不同,因为在执行SQL时,MySQL只能使用一个索引,会从多个单列索引中选择一个限制最为严格的索引(经指正,在MySQL5.0以后的版本中,有“合并索引”的策略,翻看了《高性能MySQL 第三版》,书作者认为:还是应该建立起比较好的索引,而不应该依赖于“合并索引”这么一个策略)。

    • “合并索引”策略简单来讲,就是使用多个单列索引,然后将这些结果用“union或者and”来合并起来

参考资料:

MySQL学习之——索引(普通索引、唯一索引、全文索引、索引匹配原则、索引命中等)

二、锁

在mysql中的锁看起来是很复杂的,因为有一大堆的东西和名词:排它锁,共享锁,表锁,页锁,间隙锁,意向排它锁,意向共享锁,行锁,读锁,写锁,乐观锁,悲观锁,死锁。这些名词有的博客又直接写锁的英文的简写--->X锁,S锁,IS锁,IX锁,MMVC...

锁的相关知识又跟存储引擎,索引,事务的隔离级别都是关联的....

这就给初学数据库锁的人带来不少的麻烦~~~于是我下面就简单整理一下数据库锁的知识点,希望大家看完会有所帮助。

2.1为什么需要学习数据库锁知识

不少人在开发的时候,应该很少会注意到这些锁的问题,也很少会给程序加锁(除了库存这些对数量准确性要求极高的情况下)

一般也就听过常说的乐观锁和悲观锁,了解过基本的含义之后就没了~~~

定心丸:即使我们不会这些锁知识,我们的程序在一般情况下还是可以跑得好好的。因为这些锁数据库隐式帮我们加了

  • 对于UPDATE、DELETE、INSERT语句,InnoDB自动给涉及数据集加排他锁(X)
  • MyISAM在执行查询语句SELECT前,会自动给涉及的所有表加读锁,在执行更新操作(UPDATE、DELETE、INSERT等)前,会自动给涉及的表加写锁,这个过程并不需要用户干预

只会在某些特定的场景下才需要手动加锁,学习数据库锁知识就是为了:

  • 能让我们在特定的场景下派得上用场
  • 更好把控自己写的程序
  • 在跟别人聊数据库技术的时候可以搭上几句话
  • 构建自己的知识库体系!在面试的时候不虚

2.2表锁简单介绍

首先,从锁的粒度,我们可以分成两大类:

  • 表锁

    • 开销小,加锁快;不会出现死锁;锁定力度大,发生锁冲突概率高,并发度最低
  • 行锁

    • 开销大,加锁慢;会出现死锁;锁定粒度小,发生锁冲突的概率低,并发度高

不同的存储引擎支持的锁粒度是不一样的:

  • InnoDB行锁和表锁都支持
  • MyISAM只支持表锁

InnoDB只有通过索引条件检索数据才使用行级锁,否则,InnoDB将使用表锁

  • 也就是说,InnoDB的行锁是基于索引的

表锁下又分为两种模式

  • 表读锁(Table Read Lock)
  • 表写锁(Table Write Lock)
  • 从下图可以清晰看到,在表读锁和表写锁的环境下:读读不阻塞,读写阻塞,写写阻塞

    • 读读不阻塞:当前用户在读数据,其他的用户也在读数据,不会加锁
    • 读写阻塞:当前用户在读数据,其他的用户不能修改当前用户读的数据,会加锁!
    • 写写阻塞:当前用户在修改数据,其他的用户不能修改当前用户正在修改的数据,会加锁!

从上面已经看到了:读锁和写锁是互斥的,读写操作是串行

  • 如果某个进程想要获取读锁,同时另外一个进程想要获取写锁。在mysql里边,写锁是优先于读锁的
  • 写锁和读锁优先级的问题是可以通过参数调节的:max_write_lock_countlow-priority-updates

值得注意的是:

The LOCAL modifier enables nonconflicting INSERT statements (concurrent inserts) by other sessions to execute while the lock is held. (See Section 8.11.3, “Concurrent Inserts”.) However, READ LOCAL cannot be used if you are going to manipulate the database using processes external to the server while you hold the lock. For InnoDB tables, READ LOCAL is the same as READ
  • MyISAM可以支持查询和插入操作的并发进行。可以通过系统变量concurrent_insert来指定哪种模式,在MyISAM中它默认是:如果MyISAM表中没有空洞(即表的中间没有被删除的行),MyISAM允许在一个进程读表的同时,另一个进程从表尾插入记录。
  • 但是InnoDB存储引擎是不支持的

参考资料:

2.2行锁细讲

上边简单讲解了表锁的相关知识,我们使用Mysql一般是使用InnoDB存储引擎的。InnoDB和MyISAM有两个本质的区别:

  • InnoDB支持行锁
  • InnoDB支持事务

从上面也说了:我们是很少手动加表锁的。表锁对我们程序员来说几乎是透明的,即使InnoDB不走索引,加的表锁也是自动的!

我们应该更加关注行锁的内容,因为InnoDB一大特性就是支持行锁!

InnoDB实现了以下两种类型的行锁。

  • 共享锁(S锁):允许一个事务去读一行,阻止其他事务获得相同数据集的排他锁。

    • 也叫做读锁:读锁是共享的,多个客户可以同时读取同一个资源,但不允许其他客户修改
  • 排他锁(X锁):允许获得排他锁的事务更新数据,阻止其他事务取得相同数据集的共享读锁和排他写锁。

    • 也叫做写锁:写锁是排他的,写锁会阻塞其他的写锁和读锁

看完上面的有没有发现,在一开始所说的:X锁,S锁,读锁,写锁,共享锁,排它锁其实总共就两个锁,只不过它们有多个名字罢了~~~

Intention locks do not block anything except full table requests (for example, LOCK TABLES ... WRITE). The main purpose of intention locks is to show that someone is locking a row, or going to lock a row in the table.

另外,为了允许行锁和表锁共存,实现多粒度锁机制,InnoDB还有两种内部使用的意向锁(Intention Locks),这两种意向锁都是表锁

  • 意向共享锁(IS):事务打算给数据行加行共享锁,事务在给一个数据行加共享锁前必须先取得该表的IS锁。
  • 意向排他锁(IX):事务打算给数据行加行排他锁,事务在给一个数据行加排他锁前必须先取得该表的IX锁。
  • 意向锁也是数据库隐式帮我们做了,不需要程序员操心

参考资料:

2.2.1MVCC和事务的隔离级别

数据库事务有不同的隔离级别,不同的隔离级别对锁的使用是不同的,锁的应用最终导致不同事务的隔离级别

MVCC(Multi-Version Concurrency Control)多版本并发控制,可以简单地认为:MVCC就是行级锁的一个变种(升级版)

  • 事务的隔离级别就是通过锁的机制来实现,只不过隐藏了加锁细节

表锁中我们读写是阻塞的,基于提升并发性能的考虑,MVCC一般读写是不阻塞的(所以说MVCC很多情况下避免了加锁的操作)

  • MVCC实现的读写不阻塞正如其名:多版本并发控制--->通过一定机制生成一个数据请求时间点的一致性数据快照(Snapshot),并用这个快照来提供一定级别(语句级或事务级)的一致性读取。从用户的角度来看,好像是数据库可以提供同一数据的多个版本

快照有两个级别

  • 语句级

    • 针对于Read committed隔离级别
  • 事务级别

    • 针对于Repeatable read隔离级别

我们在初学的时候已经知道,事务的隔离级别有4种

  • Read uncommitted

    • 会出现脏读,不可重复读,幻读
  • Read committed

    • 会出现不可重复读,幻读
  • Repeatable read

    • 会出现幻读(但在Mysql实现的Repeatable read配合gap锁不会出现幻读!)
  • Serializable

    • 串行,避免以上的情况!

Read uncommitted会出现的现象--->脏读:一个事务读取到另外一个事务未提交的数据

  • 例子:A向B转账,A执行了转账语句,但A还没有提交事务,B读取数据,发现自己账户钱变多了!B跟A说,我已经收到钱了。A回滚事务【rollback】,等B再查看账户的钱时,发现钱并没有多。
  • 出现脏读的原因是因为在读的时候没有加读锁,导致可以读取出还没释放锁的记录

Read uncommitted过程:

  • 事务A读取记录(没有加任何的锁)
  • 事务B修改记录(此时加了写锁,并且还没有commit-->也就没有释放掉写锁)
  • 事务A再次读取记录(此时因为事务A在读取时没有加任何锁,所以可以读取到事务B还没提交的(没释放掉写锁)的记录

Read committed避免脏读的做法其实很简单:

  • 在读取的时候生成一个版本号,直到事务其他commit被修改了之后,才会有新的版本号

Read committed过程:

  • 事务A读取了记录(生成版本号)
  • 事务B修改了记录(此时加了写锁)
  • 事务A再读取的时候,是依据最新的版本号来读取的(当事务B执行commit了之后,会生成一个新的版本号),如果事务B还没有commit,那事务A读取的还是之前版本号的数据。

Read committed出现的现象--->不可重复读:一个事务读取到另外一个事务已经提交的数据,也就是说一个事务可以看到其他事务所做的修改

  • 注:A查询数据库得到数据,B去修改数据库的数据,导致A多次查询数据库的结果都不一样【危害:A每次查询的结果都是受B的影响的,那么A查询出来的信息就没有意思了】

上面也说了,Read committed语句级别的快照!每次读取的都是当前最新的版本

Repeatable read避免不可重复读是事务级别的快照!每次读取的都是当前事务的版本,即使被修改了,也只会读取当前事务版本的数据。

呃...如果还是不太清楚,我们来看看InnoDB的MVCC是怎么样的吧(摘抄《高性能MySQL》)

至于虚读(幻读):是指在一个事务内读取到了别的事务插入的数据,导致前后读取不一致。

  • 注:和不可重复读类似,但虚读(幻读)会读到其他事务的插入的数据,导致前后读取不一致
  • MySQL的Repeatable read隔离级别加上GAP间隙锁已经处理了幻读了

参考资料:

扩展阅读:

2.3乐观锁和悲观锁

无论是Read committed还是Repeatable read隔离级别,都是为了解决读写冲突的问题。

单纯在Repeatable read隔离级别下我们来考虑一个问题:

此时,用户李四的操作就丢失掉了:

  • 丢失更新:一个事务的更新覆盖了其它事务的更新结果

(ps:暂时没有想到比较好的例子来说明更新丢失的问题,虽然上面的例子也是更新丢失,但一定程度上是可接受的..不知道有没有人能想到不可接受的更新丢失例子呢...)

解决的方法:

  • 使用Serializable隔离级别,事务是串行执行的!
  • 乐观锁
  • 悲观锁
  1. 乐观锁是一种思想,具体实现是,表中有一个版本字段,第一次读的时候,获取到这个字段。处理完业务逻辑开始更新的时候,需要再次查看该字段的值是否和第一次的一样。如果一样更新,反之拒绝。之所以叫乐观,因为这个模式没有从数据库加锁,等到更新的时候再判断是否可以更新。
  2. 悲观锁是数据库层面加锁,都会阻塞去等待锁。

2.3.1悲观锁

所以,按照上面的例子。我们使用悲观锁的话其实很简单(手动加行锁就行了):

  • select * from xxxx for update

在select 语句后边加了 for update相当于加了排它锁(写锁),加了写锁以后,其他的事务就不能对它修改了!需要等待当前事务修改完之后才可以修改.

  • 也就是说,如果张三使用select ... for update,李四就无法对该条记录修改了~

2.3.2乐观锁

乐观锁不是数据库层面上的锁,是需要自己手动去加的锁。一般我们添加一个版本字段来实现:

具体过程是这样的:

张三select * from table --->会查询出记录出来,同时会有一个version字段

李四select * from table --->会查询出记录出来,同时会有一个version字段

李四对这条记录做修改: update A set Name=lisi,version=version+1 where ID=#{id} and version=#{version},判断之前查询到的version与现在的数据的version进行比较,同时会更新version字段

此时数据库记录如下:

张三也对这条记录修改: update A set Name=lisi,version=version+1 where ID=#{id} and version=#{version},但失败了!因为当前数据库中的版本跟查询出来的版本不一致

参考资料:

2.4间隙锁GAP

当我们用范围条件检索数据而不是相等条件检索数据,并请求共享或排他锁时,InnoDB会给符合范围条件的已有数据记录的索引项加锁;对于键值在条件范围内但并不存在的记录,叫做“间隙(GAP)”。InnoDB也会对这个“间隙”加锁,这种锁机制就是所谓的间隙锁。

值得注意的是:间隙锁只会在Repeatable read隔离级别下使用~

例子:假如emp表中只有101条记录,其empid的值分别是1,2,...,100,101


Select * from  emp where empid > 100 for update;

上面是一个范围查询,InnoDB不仅会对符合条件的empid值为101的记录加锁,也会对empid大于101(这些记录并不存在)的“间隙”加锁

InnoDB使用间隙锁的目的有两个:

  • 为了防止幻读(上面也说了,Repeatable read隔离级别下再通过GAP锁即可避免了幻读)
  • 满足恢复和复制的需要

    • MySQL的恢复机制要求:在一个事务未提交前,其他并发事务不能插入满足其锁定条件的任何记录,也就是不允许出现幻读

2.5死锁

并发的问题就少不了死锁,在MySQL中同样会存在死锁的问题。

但一般来说MySQL通过回滚帮我们解决了不少死锁的问题了,但死锁是无法完全避免的,可以通过以下的经验参考,来尽可能少遇到死锁:

  • 1)以固定的顺序访问表和行。比如对两个job批量更新的情形,简单方法是对id列表先排序,后执行,这样就避免了交叉等待锁的情形;将两个事务的sql顺序调整为一致,也能避免死锁。
  • 2)大事务拆小。大事务更倾向于死锁,如果业务允许,将大事务拆小。
  • 3)在同一个事务中,尽可能做到一次锁定所需要的所有资源,减少死锁概率。
  • 4)降低隔离级别。如果业务允许,将隔离级别调低也是较好的选择,比如将隔离级别从RR调整为RC,可以避免掉很多因为gap锁造成的死锁。
  • 5)为表添加合理的索引。可以看到如果不走索引将会为表的每一行记录添加上锁,死锁的概率大大增大。

参考资料:

2.6锁总结

上面说了一大堆关于MySQL数据库锁的东西,现在来简单总结一下。

表锁其实我们程序员是很少关心它的:

  • 在MyISAM存储引擎中,当执行SQL语句的时候是自动加的。
  • 在InnoDB存储引擎中,如果没有使用索引,表锁也是自动加的。

现在我们大多数使用MySQL都是使用InnoDB,InnoDB支持行锁:

  • 共享锁--读锁--S锁
  • 排它锁--写锁--X锁

在默认的情况下,select是不加任何行锁的~事务可以通过以下语句显示给记录集加共享锁或排他锁。

  • 共享锁(S):SELECT * FROM table_name WHERE ... LOCK IN SHARE MODE
  • 排他锁(X):SELECT * FROM table_name WHERE ... FOR UPDATE

InnoDB基于行锁还实现了MVCC多版本并发控制,MVCC在隔离级别下的Read committedRepeatable read下工作。MVCC能够实现读写不阻塞

InnoDB实现的Repeatable read隔离级别配合GAP间隙锁已经避免了幻读!

  • 乐观锁其实是一种思想,正如其名:认为不会锁定的情况下去更新数据,如果发现不对劲,才不更新(回滚)。在数据库中往往添加一个version字段来实现。
  • 悲观锁用的就是数据库的行锁,认为数据库会发生并发冲突,直接上来就把数据锁住,其他事务不能修改,直至提交了当前事务

参考资料:

三、总结

本文主要介绍了数据库中的两个比较重要的知识点:索引和锁。他俩可以说息息相关的,锁会涉及到很多关于索引的知识~

我个人比较重视对整体知识点的把控,一些细节的地方可能就没有去编写了。在每一个知识点下都会有很多的内容,有兴趣的同学可以在我给出的链接中继续阅读学习。当然了,如果有比较好的文章和资料也不妨在评论区分享一下哈~

我只是在学习的过程中,把自己遇到的问题写出来,整理出来,希望可以对大家有帮助。如果文章有错的地方,希望大家可以在评论区指正,一起学习交流~

参考资料:

  • 《高性能MySQL 第三版》
如果文章有错的地方欢迎指正,大家互相交流。习惯在微信看技术文章,想要获取更多的Java资源的同学,可以关注微信公众号:Java3y。为了大家方便,刚新建了一下qq群:742919422,大家也可以去交流交流。谢谢支持了!希望能多介绍给其他有需要的朋友

文章的目录导航

查看原文

GeekGhc 关注了专栏 · 2018-08-02

Swoole

PHP的协程框架

关注 5880

GeekGhc 赞了文章 · 2018-02-19

【377天】跃迁之路——程序员高效学习方法论探索系列(实验阶段135-2018.02.17)

@(一只心中无码的程序员)专栏

实验说明

  1. 从2017.10.6起,开启这个系列,目标只有一个:通过探索新的学习方法,用2年的时间,实现2.5倍速的成长,获得普通程序员>= 5年的技术水平。
  2. 实验期2年(2017.10.06 - 2019.10.06)
  3. 我将以自己为实验对象。
  4. 我将开源我的学习方法,方法不断更新迭代,全程记录分享
  5. 实验结束后我将请5位以上资深程序员判断我是否达成目标。
  6. 本实验旨在探索新方法,所涉及的学习方法未经科学研究的方法考证,主观性极强,请谨慎使用。

跃迁方法论(20180119V1.9)

  1. 语言学习

    1. 每学习一个新语法,便识别其语言特性,归类形成脑图。【解决:知识迁移问题】
    2. 语言学习要对比进行,将不同语言的语言特性进行对比
  2. 刻意练习

    1. 思维训练【解决:归纳总结,避免低水平重复训练】

      1. 若遇到新知,先写伪代码,然后提取思路上的共性,形成思路脑图。【解决:归纳总结,构建知识复利需要的基石】
      2. 将编程思维层面的套路,背诵下来,形成表格,每次编程前,严格按步骤填写。【解决:思维混乱问题,构建结构化的编程思维】    
    2. 动手练习

      1. 若遇到旧知,继续做,直到大脑可熟练提取思路脑图,无排斥感(本能自我、感性自我认可,不再恐惧),便放弃继续练习该题【解决:低水平重复训练问题】
      2. 记录题集(错题、难题)【解决:本能自我、感性自我对未知领域的恐惧】
  3. 实践项目

    1. 选择何种方案是编程的第一大难关
    2. 实践项目做完后总结脑图

      1. 思路脑图——用于记录项目关键功能【解决:项目思路混乱,不结构化问题】    
          1. 关键功能必须书写            
              1. 底层原理                
              2. 实现步骤            
          2. 遇见问题必须书写           
              1. 导致原因                
              2. 纠正方式                        
      2. 技巧脑图——用于记录项目过程中具有通用性、工具性的编程技巧【解决:项目碎片知识遗忘问题】
  4. 技术资料

    1. 计算机基础系列
    2. 重构系列
    3. 架构师成长系列
  5. 前沿科技
  6. 复习【解决:遗忘问题】

    1. 每6天复习一次题集,看了即可
    2. 每天抽取4个不同时间,复习4大脑图,具体时间不限,完成一个便奖励积分【参见游戏化积分系统】

      1. 最低要求——看了即可
      2. 正常要求——从头浏览一遍
      3. 最高要求——在前两张基础上,不限制
  7. 游戏化积分系统【从2017.11.16引入游戏化的设计理念,全方位量化生活,持续激励本能自我、感性自我】

    1. 积分原则

      1. 任何情况都不可减分【不可对本能自我与感性自我进行任何负面激励】
      3. 积分奖励兑换物质奖励为辅,精神激励为主
    2. 积分规则

      1. 必做任务
          1. 每天完成《我爱刷题系列》更新——10分
          2. 每天完成《每日项目总结系列》更新——10分
          3. 每天复习脑图(保底1次,最佳4次)——积分规则如下:
              1. 习惯养成期(2017.11.15——2018.05.15)
                  1. 每天第1次复习奖励积分5+1分【额外的1分用于奖励【早上开始行动】这件事】
                  2. 每天第2次复习奖励积分5分
                  3. 每天第3次复习奖励积分5分
                  4. 每天第4次复习奖励积分5+1分【额外的1分用于奖励【晚上有始有终】这件事】
              2. 习惯稳定期(2018.05.15之后)
                  1. 每天第1次复习奖励积分3+1分【额外的1分用于奖励【早上开始行动】这件事】
                  2. 每天第2次复习奖励积分3分
                  3. 每天第3次复习奖励积分3分
                  4. 每天第4次复习奖励积分3+1分【额外的1分用于奖励【晚上有始有终】这件事】
          4. 每天与人分享——10分
          5. 每天拒绝诱惑一次——10分
          6. 每3天运动一次(跑步>2公里,其它>30分钟)——10分
          7. 独立思考训练(公众号每2-7天更新一次)——10分
          8. 每6天复习题集一次——10分
          9. 早睡(11点上床)
              10. 习惯养成期(2017.12.9——2018.06.09)——30分
              11. 习惯稳定期(2018.06.09之后)——10分
          10. 早起(6点下床)
              11. 习惯养成期(2017.12.9——2018.06.09)——30分
              11. 习惯稳定期(2018.06.09之后)——10分
      2. 选做任务
          1. 解决一个困难的问题——15分
          2. 做一件有意义,但内心恐惧的事情——20分
          3. 主动心流一次——20分
          4. 关注重要的人成长一次——20分
          5. 总结脑图一次——20分
      
    3. 积分奖励

      1. 用于兑换时间自由支配权(不含每日必做任务),限定时间内,随心,随性,想干嘛干嘛(但必须保证至少有一件长期坚持的事情不中断)
          1. 一天时间支配权——10000积分
      2. 用于兑换物质享乐
      
  1. 其它说明

    1. 每日必做任务的底线是打卡,非客观不可抗力,绝对不可因主观原因中断。
    2. 针对主观原因的【弹性原则】

      1. 量大于质——若因主观原因,对本能自我与感性自我失去控制,请用最后一丝理性自我的力量,完成每日任务的打卡,不求质量,仪式感不可破。【原理:身体会恢复,情绪会消散,不用担心自己任务质量从此变差,人是无法容忍长期做无意义事情的,只要坚持,最终本能自我和感性自我会想办法帮你把任务质量提上来】

实验记录

  1. 语言学习
  2. 刻意练习

    1. 刻意练习(思维训练)

      1. 知识点总结脑图

        1. Java

          1. JavaEE知识点复习脑图合集
          2. final相关知识点(20171026v1.0)
      2. 思路总结脑图

        1. Java

          1. 字符串处理(思路总结脑图20171012V1.0)
          2. 坦克大战(思路总结脑图20171016v1.0)
          3. 其它(思路总结脑图20171023V1.0)
      3. 技巧总结脑图

        1. Java

          1. 坦克大战(技巧总结脑图20171021v1.1)
        2. JavaScript

          1. 常用网页动态效果(20171030v1.1)
      4. 易混点总结脑图

        1. Java

          1. 子父类、继承多态、静态非静态、成员变量与成员方法之间的关系(20171023v1.0)
          2. 生产者、消费者、缓冲区模型间的关系及使用(20171023v1.0)
          3. 重载、重写的区别(20171026v1.0)
          4. wait、sleep的区别(20171026v1.0)
    2. 刻意练习(动手练习)

      1. Java

        1. 我爱刷题系列汇总(1-50)【2017.09.27-2017.11.23】
        2. 我爱刷题系列汇总(51-100)【2017.11.24-2018.01.12】
        3. 我爱刷题系列101(2018.01.13)
        4. 我爱刷题系列102(2018.01.14)
        5. 我爱刷题系列103(2018.01.15)
        6. 我爱刷题系列104(2018.01.16)
        7. 我爱刷题系列105(2018.01.17)
        8. 我爱刷题系列106(2018.01.18)
        9. 我爱刷题系列107(2018.01.19)
        10. 我爱刷题系列108(2018.01.20)
        11. 我爱刷题系列109(2018.01.21)
        12. 我爱刷题系列110(2018.01.22)
        13. 我爱刷题系列111(2018.01.23)
        14. 我爱刷题系列112(2018.01.24)
        15. 我爱刷题系列113(2018.01.25)
        16. 我爱刷题系列114(2018.01.26)
        17. 我爱刷题系列115(2018.01.27)
        18. 我爱刷题系列116(2018.01.28)
        19. 我爱刷题系列117(2018.01.29)
        20. 我爱刷题系列118(2018.01.30)
        21. 我爱刷题系列119(2018.01.31)
        22. 我爱刷题系列120(2018.02.01)
        23. 我爱刷题系列121(2018.02.02)
        24. 我爱刷题系列122(2018.02.03)
        25. 我爱刷题系列123(2018.02.04)
        26. 我爱刷题系列124(2018.02.05)
        27. 我爱刷题系列125(2018.02.06)
        28. 我爱刷题系列126(2018.02.07)
        29. 我爱刷题系列127(2018.02.08)
        30. 我爱刷题系列128(2018.02.09)
        31. 我爱刷题系列129(2018.02.10)
        32. 我爱刷题系列130(2018.02.11)
        33. 我爱刷题系列131(2018.02.12)
        34. 我爱刷题系列132(2018.02.13)
        35. 我爱刷题系列133(2018.02.14)
        36. 我爱刷题系列134(2018.02.15)
        37. 我爱刷题系列135(2018.02.16)
        38. 我爱刷题系列136(2018.02.17)
  1. 项目经验

    1. 娱乐项目

      1. 总结脑图

        1. 坦克大战

          1. 坦克大战(技巧总结脑图20171021v1.1)
          2. 坦克大战(思路总结脑图20171016v1.0)
    2. 工作项目

      1. 总结脑图
      2. 每日项目总结

        1. 每日项目总结系列汇总(1-50)【2017.10.26-2017.12.14】
        2. 每日项目总结系列汇总(51-100)【2017.12.15-2018.02.02】
        3. 每日项目总结系列101(2018.02.03)
        4. 每日项目总结系列102(2018.02.04)
        5. 每日项目总结系列103(2018.02.05)
        6. 每日项目总结系列104(2018.02.06)
        7. 每日项目总结系列105(2018.02.07)
        8. 每日项目总结系列106(2018.02.08)
        9. 每日项目总结系列107(2018.02.09)
        10. 每日项目总结系列108(2018.02.10)
        11. 每日项目总结系列109(2018.02.11)
        12. 每日项目总结系列110(2018.02.12)
        13. 每日项目总结系列111(2018.02.13)
        14. 每日项目总结系列112(2018.02.14)
        15. 每日项目总结系列113(2018.02.15)
        16. 每日项目总结系列114(2018.02.16)
        17. 每日项目总结系列115(2018.02.17)
  2. 技术资料

    1. 计算机基础系列

      1. 网易云课堂大学四年计算机基础课程
    2. 重构系列

      1. 《重构-改善既有代码的设计》
  3. 前沿科技
  4. 游戏化积分系统记录

    1. 20171116【当日奖励积分112,消耗积分0,累计112】
    2. 20171117【当日奖励积分132,消耗积分0,累计244】
    3. 20171118【当日奖励积分122,消耗积分0,累计366】
    4. 20171119【当日奖励积分147,消耗积分0,累计513】
    5. 20171120【当日奖励积分142,消耗积分0,累计655】
    6. 20171121【当日奖励积分142,消耗积分0,累计797】
    7. 20171122【当日奖励积分142,消耗积分0,累计939】
    8. 20171123【当日奖励积分142,消耗积分0,累计1081】
    9. 20171124【当日奖励积分162,消耗积分0,累计1243】
    10. 20171125【当日奖励积分157,消耗积分0,累计1400】
    11. 20171126【当日奖励积分192,消耗积分0,累计1592】
    12. 20171127【当日奖励积分136,消耗积分0,累计1728】
    13. 20171128【当日奖励积分162,消耗积分0,累计1890】
    14. 20171129【当日奖励积分142,消耗积分0,累计2032】
    15. 20171130【当日奖励积分152,消耗积分0,累计2184】
    16. 20171201【当日奖励积分152,消耗积分0,累计2336】
    17. 20171202【当日奖励积分182,消耗积分0,累计2518】
    18. 20171203【当日奖励积分172,消耗积分0,累计2690】
    19. 20171204【当日奖励积分152,消耗积分0,累计2842】
    20. 20171205【当日奖励积分172,消耗积分0,累计3014】
    21. 20171206【当日奖励积分161,消耗积分0,累计3175】
    22. 20171207【当日奖励积分196,消耗积分0,累计3371】
    23. 20171208【当日奖励积分206,消耗积分0,累计3577】
    24. 20171209【当日奖励积分176,消耗积分0,累计3753】
    25. 20171210【当日奖励积分166,消耗积分0,累计3919】
    26. 20171211【当日奖励积分176,消耗积分0,累计4095】
    27. 20171212【当日奖励积分176,消耗积分0,累计4271】
    28. 20171213【当日奖励积分186,消耗积分0,累计4457 】
    29. 20171214【当日奖励积分180,消耗积分0,累计4637】
    30. 20171215【当日奖励积分165,消耗积分0,累计4802】
    31. 20171216【当日奖励积分156,消耗积分0,累计4958】
    32. 20171217【当日奖励积分166,消耗积分0,累计5124】
    33. 20171218【当日奖励积分175,消耗积分0,累计5299】
    34. 20171219【当日奖励积分175,消耗积分0,累计5474】
    35. 20171220【当日奖励积分196,消耗积分0,累计5670】
    36. 20171221【当日奖励积分195,消耗积分0,累计5865】
    37. 20171222【当日奖励积分166,消耗积分0,累计6031】
    38. 20171223【当日奖励积分196,消耗积分0,累计6227】
    39. 20171224【当日奖励积分206,消耗积分0,累计6433】
    40. 20171225【当日奖励积分191,消耗积分0,累计6624】
    41. 20171226【当日奖励积分206,消耗积分0,累计6830】
    42. 20171227【当日奖励积分185,消耗积分0,累计7015】
    43. 20171228【当日奖励积分175,消耗积分0,累计7190】
    44. 20171229【当日奖励积分205,消耗积分0,累计7395】
    45. 20171230【当日奖励积分205,消耗积分5000,累计7600-5000=2600,游戏化系统上线前积分在此归零】
    46. 20171231【当日奖励积分235 ,消耗积分0,累计2835】
    47. 20180101【当日奖励积分205,消耗积分0,累计3040】
    48. 20180102【当日奖励积分206,消耗积分0,累计3246】
    49. 20180103【当日奖励积分210,消耗积分0,累计3456】
    50. 20180104【当日奖励积分206,消耗积分0,累计3662】
    51. 20180105【当日奖励积分205,消耗积分0,累计3867】
    52. 20180106【当日奖励积分211,消耗积分0,累计4078】
    53. 20180107【当日奖励积分205 ,消耗积分0,累计4283】
    54. 20180108【当日奖励积分235,消耗积分0,累计4518】
    55. 20180109【当日奖励积分235,消耗积分0,累计4753】
    56. 20180110【当日奖励积分195,消耗积分0,累计4948】
    57. 20180111【当日奖励积分205,消耗积分0,累计5153】
    58. 20180112【当日奖励积分216,消耗积分0,累计5369】
    59. 20180113【当日奖励积分201,消耗积分0,累计5570 】
    60. 20180114【当日奖励积分222,消耗积分0,累计5792】
    61. 20180115【当日奖励积分216,消耗积分0,累计6008】
    62. 20180116【当日奖励积分196,消耗积分0,累计6204】
    63. 20180117【当日奖励积分216,消耗积分0,累计6420】
    64. 20180118【当日奖励积分211,消耗积分0,累计6631】
    65. 20180119【当日奖励积分221,消耗积分0,累计6852】
    66. 20180120【当日奖励积分221,消耗积分0,累计7073】
    67. 20180121【当日奖励积分195,消耗积分0,累计7268】
    68. 20180122【当日奖励积分205,消耗积分0,累计7473】
    69. 20180123【当日奖励积分206,消耗积分0,累计7679】
    70. 20180124【当日奖励积分206,消耗积分0,累计7885】
    71. 20180125【当日奖励积分251,消耗积分0,累计8136】
    72. 20180126【当日奖励积分211,消耗积分0,累计8347】
    73. 20180127【当日奖励积分211,消耗积分0,累计8558】
    74. 20180128【当日奖励积分191,消耗积分0,累计8749】
    75. 20180129【当日奖励积分211,消耗积分0,累计8960】
    76. 20180130【当日奖励积分221,消耗积分,累计9181】
    77. 20180131【当日奖励积分196,消耗积分,累计9377】
    78. 20180201【当日奖励积分186,消耗积分,累计9563】
    79. 20180202【当日奖励积分206,消耗积分,累计9769】
    80. 20180203【当日奖励积分206,消耗积分,累计9975】
    81. 20180204【当日奖励积分196,消耗积分,累计10171】
    82. 20180205【当日奖励积分206,消耗积分,累计10377】
    83. 20180206【当日奖励积分176,消耗积分,累计10553】
    84. 20180207【当日奖励积分146,消耗积分,累计10699】
    85. 20180208【当日奖励积分166,消耗积分,累计10865】
    86. 20180209【当日奖励积分176,消耗积分,累计11041】
    87. 20180210【当日奖励积分146,消耗积分,累计11187】
    88. 20180211【当日奖励积分156,消耗积分,累计11343】
    89. 20180212【当日奖励积分166,消耗积分,累计11509】
    90. 20180213【当日奖励积分156,消耗积分,累计11665】
    91. 20180214【当日奖励积分156,消耗积分,累计11821】
    92. 20180215【当日奖励积分156,消耗积分,累计11977】
    93. 20180216【当日奖励积分146,消耗积分,累计12123】
    94. 20180217【当日奖励积分156,消耗积分,累计12279】
查看原文

赞 1 收藏 7 评论 2

GeekGhc 关注了用户 · 2017-11-21

有明 @youmingdot

支持一下杭州本土网站。

Laravel - 艺术家成长之路系列讲座

SF | 有明的技术交流群,欢迎大家加入交流分布式、服务化、设计模式、算法、Laravel、Docker、Swoole。QQ 群:666093315,进群还有讲座优惠福利

关注 1300

GeekGhc 关注了用户 · 2017-10-11

Aresn @aresn

正直 进取 合作 创新

关注 1750

GeekGhc 赞了文章 · 2017-08-31

我们为什么要尝试前后端分离

这不是一篇纯技术文章,而是一篇分享我个人在前后端分离路上收获的点点滴滴的文章,以此来为准备尝试前后端分离或者想了解前后端分离的童鞋做一个大体的讲解。

尝试与改变

如果你没有尝试过前后端分离的工作流程,那么可以先试想一下这样的流程改变:

把流程从

PM:“我要这个功能”
后端:“这个先找前端做个模板”
前端:“模板做完了”
后端:“我来对接一下,这里样式不对”
前端:“我改完了”
后端:“功能交付”
PM:“春节要加这个活动”
后端:“这个先找前端改个模板”
前端:“模板做完了”
后端:“我来对接一下,这里样式不对”
前端:“我改完了”
后端:“功能交付”

变成

PM:“我要这个功能”
前端:“我要接口”
后端:“接口完成了”
前端:“我来对接一下,功能交付”
PM:“春节要加这个活动”
前端:“需要增加接口”
后端:“接口完成了”
前端:“我来对接一下,功能交付”

由此可见,前后端分离的主要概念就是:后台只需提供API接口,前端调用AJAX实现数据呈现。

现状与分歧

作为一名前端开发人员,我们应该尝试一些新颖的技术,完善每一个细节性的问题,不断突破自我。虽然前后端分离已经算不上什么新颖的技术或思路,但是目前很多后台开发人员甚至前端开发人员都没有接触过。

据我个人的了解,如果在一个部门里,部门人员全是后台开发人员,前端的一些页面也是由后台人员完成的,那么前后端分离对于他们而言可能是一片未知的领域,项目大多是前后端强耦合的,甚至不存在前端的概念。

在不重视前端的公司或部门,不了解前后端分离这也无可厚非。在我刚进入一个全是后台开发人员的部门的时候,整个部门就我一个前端,我刚开始的主要职责就是负责项目前端页面的制作和JS功能的实现,虽然部门有前后端分离的意识,但都不知该如何去实践。在那时,部门的后台人员认为前后端分离就是后台不再需要写HTML和JS了,可以交给前端来做了,然而这只能叫做前后端分工。

以上讲述的是一种情况: 不了解前后端分离,也不知如何去实践的。下面还有一种情况:了解前后端分离,但不想去尝试的。

针对第二种情况,很多人也做过相应的解释,其实这就涉及到“前后端分离的利弊”问题。很多后台人员会认为自己所做的那一套没有问题,即便后台套用前端html也是司空见惯,一直是大势所趋,后台MVC框架也是这么推荐使用的,很合理。这时候前端开发人员在部门中的话语权往往是不够的,或者认为后台开发人员的意见永远是对的,没有主观性。

相反,也有可能是后台开发人员非常推荐前后端分离,而前端开发人员不想去实践的。这时候前端会认为后台开发人员在瞎折腾,之前前后端不分离项目做起来都很顺利,分离了反而会给自己带来额外的工作量和学习成本,而这就取决于前端的技术能力和见识了。

当然,这也是我个人认为的前后端分离所存在的一些现状和分歧所在。

场景与要求

对于前后端分离的应用场景,不是所有的场景都适合,但是大多数项目都能够通过前后端分离来实现。

由于我主要从事企业级后台应用的前端开发工作,个人认为对于后台应用的开发来说,前后端分离带来的利是远大于弊的。

大多数后台应用我们都可以做成SPA应用(单页应用),而单页应用最主要的特点就是局部刷新,这通过前端控制路由调用AJAX,后台提供接口便可以实现,而且这样的方式用户体验更加友好,网页加载更加快速,开发和维护成本也降低了不少,效率明显提升。

同样的,在展示类网站和移动APP页面中前后端分离也同样试用。前后端不分离的情况下,服务端要单独针对Web端做处理,返回完整HTML,这样势必增加服务端的复杂度,可维护性差,而web端需要加载完整的HTML,一定程度上影响网页性能,这对于移动端性能为王的地方非常的不友好。

随着前端技术的发展和迭代,前端MVC框架应运而生,利用目前主流的前端框架,如React、Vue、Angular等我们可以轻松的构建起一个无需服务器端渲染就可以展示的网站,同时这类框架都提供了前端路由功能,后台可以不再控制路由的跳转,将原本属于前端的业务逻辑全部丢给前端,这样前后端分离可以说是最为彻底。下面是一段前端控制路由的代码:

'use strict'

export default function (router) {
    router.map({
        '/': {
            component: function (resolve) {
                require(['./PC.vue'], resolve)
            }
        },
        '/m/:params': {
            component: function (resolve) {
                require(['./Mobile.vue'], resolve)
            }
        },
        '/p': {
            component: function (resolve) {
                require(['./PC.vue'], resolve)
            },
            subRoutes: {
                '/process/:username': {
                    component: function (resolve) {
                        require(['./components/Process.vue'], resolve)
                    }
                }
            }
        }
    })
}

前后端分离的实现对技术人员尤其是前端人员的要求会上升一个层次,前端的工作不只是切页面写模板或是处理一些简单的js逻辑,前端需要处理服务器返回的各种数据格式,还需要掌握一系列的数据处理逻辑、MVC思想和各种主流框架。

优势与意义

对于前后端分离的意义我们也可以看做是前端渲染的意义,我主要总结了下面四点:

1. 彻底解放前端

前端不再需要向后台提供模板或是后台在前端html中嵌入后台代码,如:

<!--服务器端渲染 -->
<select>
    <option value=''>--请选择所属业务--</option>
    {% for p in p_list %}
    <option value="{{ p }}">{{ p }}</option>
    {% endfor %}
</select>

这是前后端耦合的,可读性差。

<!--前端渲染 -->
<template>
    <select id="rander">
        <option value=''>--请选择所属业务--</option>
        <option v-for="list in lists" :value="list" v-text="list"></option>
    </select>
</template>

<script>
export default {
    data: {
        return {
            lists: ['选项一', '选项二', '选项三', '选项四']
        }
    },
    ready: function () {
        this.$http({
            url: '/demo/',
            method: 'POST',
        })
        .then(function (response) {
            this.lists = response.data.lists // 获取服务器端数据并渲染
        })
    }
}
</script>

上面是前端渲染的一段代码,前端通过AJAX调用后台接口,数据逻辑放在前端,由前端维护。

2. 提高工作效率,分工更加明确

前后端分离的工作流程可以使前端只关注前端的事,后台只关心后台的活,两者开发可以同时进行,在后台还没有时间提供接口的时候,前端可以先将数据写死或者调用本地的json文件即可,页面的增加和路由的修改也不必再去麻烦后台,开发更加灵活。

3. 局部性能提升

通过前端路由的配置,我们可以实现页面的按需加载,无需一开始加载首页便加载网站的所有的资源,服务器也不再需要解析前端页面,在页面交互及用户体验上有所提升。

4. 降低维护成本

通过目前主流的前端MVC框架,我们可以非常快速的定位及发现问题的所在,客户端的问题不再需要后台人员参与及调试,代码重构及可维护性增强。

心得与体会

一路走来,项目一个接着一个,从一开始的后台控制路由、后台渲染页面到现在的前端控制路由、前端渲染数据,工作流程和方式都发生了很大的变化。每当遇到下面情形的时候,我都会为前后端分离带来的优势而感慨一番:

  • 项目一开始制作前端页面的时候,我不再需要后台给我配置服务器环境了

  • 项目的前端文件可以在需要调用后台接口的时候丢进服务器就好了,完全不需要事先放进去

  • 增加一个项目页面需要配置路由的时候不再需要让后台同事给我加了,自己前端搞定

  • 前端文件里不再掺杂后台的代码逻辑了,看起来舒服多了

  • 页面跳转比之前更加流畅了,局部渲染局部加载非常快速

  • 页面模板可以重复使用了,前端组件化开发提高了开发效率

等等。面对快速发展的前端,我们应该去适应其带来的工作方式和流程的改变,目前的前后端分离的工作方式必然是今后的趋势所在,作为一个前端开发人员,我们应当承担这个普及前端新知识和改变现状的职责。

只有尝试了才知道适不适合,只有切身体会才能辨别谁是谁非,本文并非推崇一定要前后端分离,而是希望大家在合适的应用场景下去尝试前后端分离,在丰富经验的同时或许也会擦出火花。

本文地址:https://segmentfault.com/a/11...
博客园:http://www.cnblogs.com/luozhi...

查看原文

赞 55 收藏 151 评论 35

认证与成就

  • 获得 90 次点赞
  • 获得 36 枚徽章 获得 1 枚金徽章, 获得 9 枚银徽章, 获得 26 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

  • 博客

    Jekyll构建的静态博客

  • Zhihu

    laravel+vue实现知乎的答题功能

  • ISpace Community

    ISpace 社区 文章 帖子 个人交流

  • Flash-For-Laravel

    基于laravel的Flash消息提示扩展包

  • AdminLTE-For-Laravel

    基于AdminLTE为Laravel提供后台模板

  • 知加SPA

    类似知加的一款SPA应用 采用前后端分离 本地laravel项目作为后端api provider 仅做学习

注册于 2016-10-10
个人主页被 1.6k 人浏览