.NET Core 1.0 RC2 历险之旅

文章背景:对于.NET Core大家应该并不陌生, 从它被 宣布 到现在已经有1-2年的时间了,其比较重要的一个版本1.0 RC2 也即将发布。.Net Core从一个一个的测试版到现在的RC2,经历了很多个大大小小的变化。特别是在RC1到RC2的更新之中,.NET Core命令行工具(dotnet cli)从 dnx 变为 dotnet,并且废除了 DNVM 和 DNU,使得 .NET Core 的开发变得更为简单,其相关工具链也基本成型。虽然网上关于.NET Core的示例项目不在少数,而且微软官方也提供了不少示例项目,但大多针对的是.NET Core的不同版本,因此很多示例项目并不是能很容易的运行起来。所以我决定写一篇针对RC2这个版本的.NET Core入门文章并提供一些能直接运行的示例项目

下载安装 .NET Core SDK

从 dotnet cli github项目主页找到最新版的.Net Core SDK下载:

https://github.com/dotnet/cli

例如 Mac OS X的最新版的.NET Core SDK的下载地址为:

https://dotnetcli.blob.core.windows.net/dotnet/beta/Installers/Latest/dotnet-dev-osx-x64.latest.pkg

安装前请确认当前系统是否已经安装了老版本的.NET Core, 如果已经安装,请先卸载。

如在Mac OS X上已安装的话,请运行如下命令删除:

sudo rm -rf /usr/local/share/dotnet

在Mac OS X上安装之前请先确保 openssl 已经被安装了:

brew install openssl

开发工具 Visual Studio Code 及其 C# 插件安装

如不准备使用VSCode进行开发的话,请忽略此部分。我不确定最新版本的 Visual Studio 2015 Update 2 是否对.NET Core 1.0 RC2有很好的支持。

  1. 从官方网站下载安装 VSCode

    https://code.visualstudio.com/

  2. 安装VSCode C#插件

    由于支持 RC2 的 C# 插件 v1.0 还未正式发布到 VS Code extension 仓库里, 因此你只能手动从github下载并安装:

    https://github.com/OmniSharp/omnisharp-vscode/releases

    Mac OS X 下通过 VSCode 打开下载下来的文件即可。

    等到 VS Code C# 插件 v1.0 版本正式发布了,你就可以通过VSCode的命令窗口来安装 C# 支持了。详细操作如下:

    运行VSCode, 然后使用快捷键 ⌘ + P 启动快速打开命令窗口,然后输入如下命令安装C#扩展。最新版的csharp扩展已支持 RC2 的.NET程序的调试。

    ext install csharp

使用.NET CLI (dotnet) 创建,编译和运行项目

  1. 创建项目

首先在控制台/Terminal下进入你要创建项目的目录,然后运行如下命令:

    dotnet new
    

dotnet cli 创建新项目的时候支持项目类型参数-t,但当前只支持Console参数。:(

运行之后会生成两个文件

    project.json    -   类似于.NET Framework里的项目文件
    Program.cs      -   程序启动入口
    

使用restore命令下载依赖

    dotnet restore

如出现网络错误导致restore失败的情况请重试几次,貌似这种情况比较少。

如发现类似下面的Warning也请不要惊慌,这是由于CLI的版本号与下载下来的.NET Core类库的版本号不一致导致的,这种情况不会影响编译和运行。

    warn : Dependency specified was Microsoft.NETCore.App (>= 1.0.0-rc2-3002464) but ended up with Microsoft.NETCore.App 1.0.0-rc2-3002468.
  1. 编译项目

在控制台或Terminal打开项目所在目录,运行如下命令编译:

    dotnet build
    

如出现如下类似编译错误:

   error NU1002: The dependency Microsoft.CodeAnalysis.Common 1.2.0-beta1-20160202-02 does not support framework .NETCoreApp,Version=v1.0
   error NU1002: The dependency Microsoft.CodeAnalysis.CSharp 1.2.0-beta1-20160202-02 does not support framework .NETCoreApp,Version=v1.0
   

出现这个问题的可能原因是NuGet上通过版本号匹配到的依赖包并不能使用,你需要做如下操作之后再重新restore一下。

a. 在项目根目录新增文件 NuGet.config, 并写入以下内容:

    <?xml version="1.0" encoding="utf-8"?>
    <configuration>
        <packageSources>
            <!--To inherit the global NuGet package sources remove the <clear/>line below -->
            <clear/>
            <add key="dotnet-core" value="https://dotnet.myget.org/F/dotnet-core/api/v3/index.json"/>
            <add key="api.nuget.org" value="https://api.nuget.org/v3/index.json"/>
        </packageSources>
    </configuration>
    

b. 打开 project.json 文件将 dependencies 节点中的 Microsoft.NETCore.App 的版本好更改为 1.0.0-rc2-*:

    "dependencies": {
        "Microsoft.NETCore.App": {
            "type": "platform",
            "version": "1.0.0-rc2-*"
        }
    }
    

c. 然后再重新运行 dotnet restore 之后编译

  1. 运行项目

    在控制台或Terminal打开项目所在目录,运行如下命令运行:

    dotnet run

    如遇如下错误,

    Expected to load libhostpolicy.dylib from [/usr/local/share/dotnet/shared/Microsoft.NETCore.App/1.0.0-rc2-3002468]
    This may be because the targeted framework ["Microsoft.NETCore.App": "1.0.0-rc2-3002468"] was not found.

    也不要慌张,到目录 bin/Debug/netcoreapp1.0中 找到文件 *.runtimeconfig.json, 将其中 runtime 版本号修改为与本机 CLI 版本号一致即可。

    {
        "runtimeOptions": {
            "framework": {
                "name": "Microsoft.NETCore.App",
                "version": "1.0.0-rc2-3002485"
            }
        }
    }

    如本机 dotnet –version 命令返回值为 “1.0.0-rc2-002485”,则应runtime config中的版本号应替换为“1.0.0-rc2-3002485”。

    然后再尝试运行 dotnet run

  2. 调试项目 (Visual Studio Code)

    使用 VSCode 打开项目所在文件夹之后,VSCode 会问你是否添加启用项目调试相关的文件,你选OK之后目录下会新增文件夹“.vscode”,其中会包含两个文件:

    launch.json
    tasks.json

    当你发现无法调试失败的时候,你可以到 launch.json 文件,检查启动所指向的文件是否正确:

    {
        "name": ".NET Core Launch (console)",
        ...
        "program": "${workspaceRoot}/bin/Debug/netcoreapp1.0/netcore.dll",
        ...
    }

使用 .NET Core 进行 ASP.NET MVC 开发

虽然 dotnet cli 并没有提供直接创建 web/mvc项目的选项,但是我们还是可以手动来创建 mvc 项目的。

  1. 首先是更新 project.json 来支持 mvc:
    {
        "version": "1.0.0-*",
        "content": [
            "wwwroot",
            "Views"
        ],
        "compilationOptions": {
            "preserveCompilationContext": true,
            "emitEntryPoint": true,
            "debugType": "portable"
        },
        "dependencies": {
            "Microsoft.AspNetCore.Diagnostics": "1.0.0-*",
            "Microsoft.AspNetCore.Mvc": "1.0.0-*",
            "Microsoft.AspNetCore.Mvc.TagHelpers": "1.0.0-*",
            "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0-*",
            "Microsoft.AspNetCore.Server.Kestrel": "1.0.0-*",
            "Microsoft.AspNetCore.StaticFiles": "1.0.0-*",
            "Microsoft.Extensions.Logging.Console": "1.0.0-*",
            "Microsoft.NETCore.App": {
                "type": "platform",
                "version": "1.0.0-rc2-*"
            }
        },
        "frameworks": {
            "netcoreapp1.0": {
            "imports": [
                "portable-net45+wp80+win8+wpa81+dnxcore50"
            ]
            }
        },
        "tools": {
            "Microsoft.AspNetCore.Server.IISIntegration.Tools": {
            "version": "1.0.0-*",
            "imports": "portable-net45+wp80+win8+wpa81+dnxcore50"
            }
        },
        "scripts": {
            "postpublish": "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%"
        }
    }
  2. 在NuGet.config 文件中增加 ASP.NET 的包的下载地址,如此文件不存在请先添加:
    <?xml version="1.0" encoding="utf-8"?>
    <configuration>
        <packageSources>
            <!--To inherit the global NuGet package sources remove the <clear/> line below -->
            <clear />
            <add key="dotnet-core" value="https://dotnet.myget.org/F/dotnet-core/api/v3/index.json" />
            <add key="api.nuget.org" value="https://api.nuget.org/v3/index.json" />
            <add key="AspNetCI" value="https://www.myget.org/F/aspnetcirelease/api/v3/index.json" />
        </packageSources>
    </configuration>
  3. 增加 Startup.cs 文件 然后在 Program.cs 增加启动代码:

    Startup.cs

    using Microsoft.AspNetCore.Builder;
    using Microsoft.AspNetCore.Hosting;
    using Microsoft.Extensions.DependencyInjection;
    using Microsoft.Extensions.Logging;
    
    namespace HelloMvc
    {
        public class Startup
        {
            public void ConfigureServices(IServiceCollection services)
            {
                // 注册MVC相关服务到ASP.NET Core的反转控制器
                services.AddMvc();
            }
    
            public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
            {
                loggerFactory.AddConsole(LogLevel.Debug);
    
                //启用静态文件支持
                app.UseStaticFiles();
    
                if (env.IsDevelopment())
                {
                    app.UseDeveloperExceptionPage();
                }
    
                // 启用 Mvc 并定义默认的路由
                app.UseMvc(routes =>
                {
                    routes.MapRoute(
                        name: "default",
                        template: "{controller=Home}/{action=Index}/{id?}");
                });
            }
        }
    }

    Program.cs

    using System;
    using System.IO;
    using Microsoft.AspNetCore.Hosting;
    
    namespace HelloMvc
    {
        public class Program
        {
            public static void Main(string[] args)
            {
                var host = new WebHostBuilder()
                            .UseKestrel()
                            .UseContentRoot(Directory.GetCurrentDirectory())
                            .UseDefaultHostingConfiguration(args)
                            .UseIISIntegration()
                            .UseStartup<Startup>()
                            .Build();
    
                host.Run();
            }
        }
    }
  4. MVC 其它

    ASP.NET Core中 MVC 具体的开发方法,请参考官方文档 https://docs.asp.net/en/latest/mvc/index.html 来学习使用,我在这里就不再累述了。
    完整示例可参考我在github上的示例项目:https://github.com/kerryjiang/dotnetcore-samples/tree/master/mvc

    MVC项目也可通过 dotnet run 命令运行,还可以使用VSCode进行调试。

在 .NET Core 中使用 EntityFramework + Sqlite

.NET Core 中的 EntityFramework 也在 RC2 也有较大的变化。包的名字从 “EntityFramework.” 变化为 “Microsoft.EntityFrameworkCore.“。
如需使用Sqlite的话,project.json的包依赖应该为:

"dependencies": {
    ...
    ...
    "Microsoft.EntityFrameworkCore": "1.0.0-*",
    "Microsoft.EntityFrameworkCore.Sqlite": "1.0.0-*",
    "Microsoft.NETCore.App": {
        "type": "platform",
        "version": "1.0.0-rc2-*"
    }
}

另外,frameworks 节点对 netcoreapp1.0 也需增加新的imports (portable-net45+win8+wp8+wpa81 和 portable-net45+win8+wp8):

"frameworks": {
    "netcoreapp1.0": {
        "imports": [
            "portable-net45+wp80+win8+wpa81+dnxcore50",
            "portable-net45+win8+wp8+wpa81",
            "portable-net45+win8+wp8"
        ]
    }
}

然后在Startup.cs中注册EF相关的服务和DbContext:

public void ConfigureServices(IServiceCollection services)
{
    services.AddEntityFramework()
            .AddEntityFrameworkSqlite()
            .AddDbContext<WebsiteDbContext>(
                options => options.UseSqlite("Data Source=./mvcefsample.sqlite")); //设置链接字符串
                
    services.AddMvc();
}

再到Configure里面初始化数据库或者启用DbMigration:

    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        ...
        
        using (var serviceScope = app.ApplicationServices.GetRequiredService<IServiceScopeFactory>().CreateScope())
        {
            var db = serviceScope.ServiceProvider.GetService<WebsiteDbContext>();
            
            // do db migrate automatically
            // db.Database.Migrate();
            
            if (db.Database.EnsureCreated())
            {
                for (int i = 0; i < 10; i++)
                {
                    var article = new Article {
                        Title = string.Format("Article {0}",  i + 1),
                        Content = string.Format("Article {0} content blabla blabla",  i + 1),
                        CreatedTime = DateTime.Now,
                        UpdatedTime = DateTime.Now
                    };
                    
                    db.Articles.Add(article);
                }
                db.SaveChanges();
            }
        }
    }

完整示例可参考我在github上的示例项目:https://github.com/kerryjiang/dotnetcore-samples/tree/master/mvc-ef

在 ASP.NET MVC Core 中使用 ASP.NET Identity

  1. ASP.NET 中身份验证是免不了的事情,首先第一步在 project.json 中添加包依赖:
    "dependencies": {
        ...
        ...
        "Microsoft.AspNetCore.Identity.EntityFrameworkCore": "1.0.0-*",
        ...
        "Microsoft.EntityFrameworkCore": "1.0.0-*",
        "Microsoft.EntityFrameworkCore.Sqlite": "1.0.0-*",
        "Microsoft.NETCore.App": {
            "type": "platform",
            "version": "1.0.0-rc2-*"
        }
    }
  2. 实现带有 Identity 支持的 DbContext:
    using Microsoft.EntityFrameworkCore;
    using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
    
    namespace MvcIdentitySample
    {
        public class ApplicationUser : IdentityUser
        {
    
        }
    
        public class WebsiteDbContext : IdentityDbContext<ApplicationUser>
        {
            public WebsiteDbContext(DbContextOptions<WebsiteDbContext> options)
                : base(options)
            {
    
            }
    
            //public DbSet<Article> Articles { get; set; }
        }
    }
  3. 然后在 Startup 中的 ConfigureSerivces 方法中注册服务:
    public void ConfigureServices(IServiceCollection services)
    {
        // register services about EF
        services.AddEntityFramework()
                .AddEntityFrameworkSqlite()
                .AddDbContext<WebsiteDbContext>(
                    options => options.UseSqlite("Data Source=./mvcidentitysample.sqlite"));
    
        // register services about ASP.NET Identity
        services.AddIdentity<ApplicationUser, IdentityRole>()
            .AddEntityFrameworkStores<WebsiteDbContext>()
            .AddDefaultTokenProviders();
    
        services.AddMvc();
    }
  4. 再到 Startup 中的 Configure 方法中启用 Identity:
    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        ...
    
        // the authentication must be configured before mvc
        app.UseIdentity();
    
        // To configure external authentication please see http://go.microsoft.com/fwlink/?LinkID=532715
        /*
        app.UseFacebookAuthentication(new FacebookOptions
        {
            AppId = "901611409868059",
            AppSecret = "4aa3c530297b1dcebc8860334b39668b"
        });
        */
    
        app.UseMvc(routes =>
        {
            routes.MapRoute(
                name: "default",
                template: "{controller=Home}/{action=Index}/{id?}");
        });
    }

注意 app.UseIdentity() 这句必须放到启用 MVC 之前,否则 Identity 无法生效,这个问题折腾了我几个小时。:(

  1. 由于 ASP.NET MVC 6 和 最新的 ASP.NET Identity 的变化,其使用方法与老版本的模版代码略有不同。

    a. 页面头部登陆状态部分页面(/Views/Shared/_LoginPartial.cshtml),由于 taghelper 的引入和 模版引擎的变化,这个页面的代码看起来会和以前有明显的不同:

    @using Microsoft.AspNetCore.Identity
    @using MvcIdentitySample
    @using MvcIdentitySample.Models
    @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
    @inject SignInManager<ApplicationUser> SignInManager
    @inject UserManager<ApplicationUser> UserManager
    
    @if (SignInManager.IsSignedIn(User))
    {
        <form asp-controller="Account" asp-action="LogOff" method="post" id="logoutForm" class="navbar-right">
            <ul class="nav navbar-nav navbar-right">
                <li>
                    <a asp-controller="Manage" asp-action="Index" title="Manage">Hello @UserManager.GetUserName(User)!</a>
                </li>
                <li>
                    <button type="submit" class="btn btn-link navbar-btn navbar-link">Log off</button>
                </li>
            </ul>
        </form>
    }
    else
    {
        <ul class="nav navbar-nav navbar-right">
            <li><a asp-controller="Account" asp-action="Register">Register</a></li>
            <li><a asp-controller="Account" asp-action="Login">Log in</a></li>
        </ul>
    }

    b. 新增 _ViewImports.cshtml (/Views/_ViewImports.cshtml) 的使用避免了重复在多个view里面增加相同的using和其它定义:

    @using Microsoft.AspNetCore.Identity
    @using MvcIdentitySample
    @using MvcIdentitySample.Models
    @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

    这样就不用在刚才的_LoginPartial.cshtml页面里增加同样的代码了。

总结

大家通过以上介绍和示例应该可以了解到,当前使用.NET Core 来进行简单的应用开发是可行的,dot cli(dotnet) 这个工具已经比上个版本(RC1)的工具简单方便了很多,而且统一的Web开发和Console开发,CLI工具本身以后应该不会有太大的变化。因为.NET Core 1.0 RC2还在开发测试阶段,有的包可能还没有发布到NuGet上,因此而造成的找不到合适包的情况属于常见问题之一,不过只要在NuGet.config中增加了合适的Package Source,这个问题就很好解决了。所以大家如果想要把自己的项目,公司的项目迁移到.NET Core, 现在就可以开始动手了,不用再等到时间不确定的 1.0 release。

另外,本文所展示的示例代码限于篇幅与排版,无法做到十分详细,例如代码中所需要的using并未提及,还需要读者来自行添加(VS里ALT+SHIFT+F10, VSC里面好像只能手动点感叹号?)。

而且此文旨在尝试.NET Core的可用性,因此并未对如ASP.NET MVC 6和 EF7相关技术做深入探讨,如需了解请查看相关技术文档:

  1. ASP.NET MVC
  2. EntityFramework

最后再提醒大家一次,本文中所涉及的代码的完整示例项目已放到 github 上,欢迎大家fork/star/send pr:

https://github.com/kerryjiang/dotnetcore-samples

作者:江振宇
出处:http://jzywh.cnblogs.com
本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接,否则保留追究法律责任的权利。

使用VS Code开发 调试.NET Core 应用程序

使用VS Code开发 调试.NET Core RC2应用程序,由于.NET Core 目前还处于预览版。

本文使用微软提供的示例进行开发及调试。

https://github.com/aspnet/cli-samples

.NET Core 介绍及说明:

https://github.com/dotnet/cli

http://dotnet.github.io/getting-started/

环境安装

本文开发的实际环境: win10 x64 VSCode 1.0

下载

https://github.com/dotnet/cli#installers-and-binaries

.NET Core SDK Installer:

https://dotnetcli.blob.core.windows.net/dotnet/beta/Installers/Latest/dotnet-dev-win-x64.latest.exe

VSCode :

https://code.visualstudio.com/

VSCode C#插件:

https://github.com/OmniSharp/omnisharp-vscode/releases

最新版: https://github.com/OmniSharp/omnisharp-vscode/releases/download/v1.0.4-rc2/csharp-1.0.4-rc2.vsix

安装好VSCode以后,打开VSCode 安装C#插件。

直接用VSCode 打开插件就可以安装了。

安装好以后 F1 会发现多了dotnet 命令,证明也就安装完成。

开发调试

下载微软的示例代码:https://github.com/aspnet/cli-samples

下载代码后,使用VSCode 打开文件夹 cli-samples/HelloMvc

F1 输入 dotnet restore

选择对应的dotnet restor (这里显示好像是插件的bug)

 

你还可以在文件夹打开命令行,输入 dotnet restore 同样可以还原相关引用。

还原相关的引用。还原好以后我们就可以进行调试了。

点击调试 程序就跑起来。

 

这样就可以下断点调试。

访问 http://localhost:5000/

 

开发

我们来添加一个新的Action

        [HttpGet("/about")]
        public IActionResult About(){
            var useragent=Request.Headers["User-Agent"];
            return Content(useragent+"\r\nabout by linezero");
        }

访问: http://localhost:5000/about

下断点调试一下程序,断点设置跟VS一样。

断下来以后,可以查看对应的属性以及值。

左侧有对应的监视器,可以查看各个值。

这样我们已经可以调试.NET Core。

腾讯防刷负责人:基于用户画像大数据的电商防刷架构

作者|颜国平,腾讯云-天御系统研发负责人。一直负责腾讯自有验证码、业务安全、防刷、账号安全等研发工作。内部支持的产品(游戏、电商、腾讯投资的O2O企业)非常广泛。在业务安全领域项目经验丰富,并且具备深度学习、大数据架构搭建等实战经验。

一、背景介绍

最近1~2年电商行业飞速发展,各种创业公司犹如雨后春笋大量涌现,商家通过各种活动形式的补贴来获取用户、培养用户的消费习惯。

但任何一件事情都具有两面性,高额的补贴、优惠同时了也催生了“羊毛党”。

“羊毛党”的行为距离欺诈只有一步之遥,他们的存在严重破环了活动的目的,侵占了活动的资源,使得正常的用户享受不到活动的直接好处。

今天主要分享下腾讯自己是如何通过大数据、用户画像、建模来防止被刷、恶意撞库的。

二、黑产现状介绍

“羊毛党”一般先利用自动机注册大量的目标网站的账号,当目标网站搞促销、优惠等活动的时候,利用这些账号参与活动刷取较多的优惠,最后通过淘宝等电商平台转卖获益。

一、羊毛党分工

他们内部有着明确的分工,形成了几大团伙,全国在20万人左右:

  1. 软件制作团伙:专门制作各种自动、半自动的黑产工具,比如注册自动机、刷单自动机等;他们主要靠出售各种黑产工具、提供升级服务等形式来获利。
  2. 短信代接平台:实现手机短信的自动收发,其实一些平台亦正亦邪,不但提供给正常的商家使用,一些黑产也会购买相关的服务。
  3. 账号出售团伙:他们主要是大量注册各种账号,通过转卖账号来获利;该团伙与刷单团伙往往属于同一团伙。
  4. 刷单团伙:到各种电商平台刷单,获取优惠,并且通过第三方的电商平台出售优惠,实现套现。

大数据架构

二、“羊毛党”从业特点

这些黑产团队,有三个特点:

  1. 专业化:专业团队、人员、机器来做。
  2. 团伙化:黑产已经形成一定规模的团伙,而且分工明确;从刷单软件制作、短信代收发平台、电商刷单到变卖套现等环节,已经形成完整的刷单团伙。
  3. 地域化:黑产刷单团伙基本分布在沿海的一些经济发达城市,比如,北京、上海、广东等城市,这或许跟发达城市更加容易接触到新事物、新观念有关。

大数据架构

三、对抗刷单的思路

对抗刷单,一般来讲主要从三个环节入手:

  1. 注册环节:识别虚假注册、减少“羊毛党”能够使用的账号量。在注册环节识别虚假注册的账号,并进行拦截和打击。
  2. 登录场景:提高虚假账号登录门槛,从而减少能够到达活动环节的虚假账号量。比如,登录环节通过验证码、短信验证码等手段来降低自动机的登录效率,从而达到减少虚假账号登录量、减轻活动现场安全压力的目的。
  3. 活动环节:这个是防刷单对抗的主战场,也是减少“羊毛党”获利的直接战场;这里的对抗措施,一般有两个方面:1)通过验证码(短信、语音)降低黑产刷单的效率。

    2)大幅度降低异常账号的优惠力度。

三、腾讯内部防刷架构

一、腾讯内部防刷的架构图

大数据架构

二、模块详细介绍

1、风险学习引擎

风险学习引擎:效率问题。由于主要的工作都是线下进行,所以线上系统不存在学习的效率问题。线上采用的都是C++实现的DBScan等针对大数据的快速聚类算法,基本不用考虑性能问题。

风险学习引擎:采用了黑/白双分类器风险判定机制。之所以采用黑/白双分类器的原因就在于减少对正常用户的误伤。

例如,某个IP是恶意的IP,那么该IP上可能会有一些正常的用户,比如大网关IP。

再比如,黑产通过ADSL拨号上网,那么就会造成恶意与正常用户共用一个IP的情况。

黑分类器:根据特征、机器学习算法、规则/经验模型,来判断本次请求异常的概率。

白分类器:判断属于正常请求的概率。

大数据架构

2、矩阵式逻辑框架

我们以黑分类器为例来剖析下分类器的整个逻辑框架。

总的来讲我们采用了矩阵式的逻辑框架,最开始的黑分类器我们也是一把抓,随意的建立一个个针对黑产的检测规则、模型。

结果发现不是这个逻辑漏过了,而是那个逻辑误伤量大,要对那一类的账号加强安全打击力度,改动起来也非常麻烦。

因此我们就设计了这个一个矩阵式的框架来解决上述问题。

大数据架构

矩阵的横向采用了Adaboost方法,该方法是一种迭代算法,其核心思想是针对同一个训练集训练不同的弱分类器,然后把这些分类器集合起来,构成一个最终的分类器。

而我们这里每一个弱分类器都只能解决一种帐号类型的安全风险判断,集中起来才能解决所有账户的风险检测。

那么在工程实践上带来三个好处:

  1. 便于实现轻重分离,比如某平台虚假账号集中在邮箱账号,策略就可以加大对邮箱账号的打击力度,影响范围也局限在邮箱帐号,而不是该平台所有的账号。
  2. 减少模型训练的难度,模型训练最大的难度在于样本的均衡性问题,拆分成子问题,就不需要考虑不同账号类型之间的数据配比、均衡性问题,大大降低了模型训练时正负样本比率的问题。
  3. 逻辑的健壮性,某一个分类器的训练出现了问题,受影响的范围不至于扩展到全局。

矩阵纵向采用了Bagging方法,该方法是一种用来提高学习算法准确度的方法,该方法在同一个训练集合上构造预测函数系列,然后以一定的方法将他们组合成一个预测函数,从而来提高预测结果的准确性。

上面讲的部分东西,理解起来会比较艰涩,这里大家先理解框架,后续再理解实现细节。

四、腾讯大数据收集纬度

大数据一直在安全对抗领域发挥着重要的作用,从我们的对抗经验来看,大数据不仅仅是数据规模很大,而且还包括两个方面:

  1. 数据广度:要有丰富的数据类型。比如,不仅仅要有社交领域的数据、还要有游戏、支付、自媒体等领域的数据,这样就提供了一个广阔的视野让我们来看待黑产的行为特点。
  2. 数据深度:黑产的对抗。我们一直强调纵深防御,我们不仅仅要有注册数据,还要有登录,以及账号的使用的数据,这样我们才能更好的识别恶意。

所以想要做风控和大数据的团队,一定要注意在自己的产品上多埋点,拿到足够多的数据,先沉淀下来。

五、腾讯大数据处理平台-魔方

我们的团队研发了一个叫魔方的大数据处理和分析的平台,底层我们集成了MySQL、MongoDB,Spark、Hadoop等技术,在用户层面我们只需要写一些简单的SQL语句、完成一些配置就可以实现例行分析。

这里我们收集了社交、电商、支付、游戏等场景的数据,针对这些数据我们建立一些模型,发现哪些是恶意的数据,并且将数据沉淀下来。

沉淀下来的对安全有意义的数据,一方面就存储在魔方平台上,供线下审计做模型使用;另一方面会做成实时的服务,提供给线上的系统查询使用。

一、腾讯用户画像沉淀方法

画像,本质上就是给账号、设备等打标签。

用户画像 = 打标签

我们这里主要从安全的角度出发来打标签,比如IP画像,我们会标注IP是不是代理IP,这些对我们做策略是有帮助的。

以QQ的画像为例,比如,一个QQ只登录IM、不登录其他腾讯的业务、不聊天、频繁的加好友、被好友删除、QQ空间要么没开通、要么开通了QQ空间但是评论多但回复少,这种号码我们一般会标注QQ养号(色情、营销),类似的我们也会给QQ打上其他标签。

标签的类别和明细,需要做风控的人自己去设定,比如:地理位置,按省份标记。性别,安男女标记。其他细致规则以此规律自己去设定。

我们看看腾讯的IP画像,沉淀的逻辑如下图:

大数据架构

一般的业务都有针对IP的频率、次数限制的策略,那么黑产为了对抗,必然会大量采用代理IP来绕过限制。

既然代理IP的识别如此重要,那我们就以代理IP为例来谈下腾讯识别代理IP的过程。

识别一个IP是不是代理IP,技术不外乎就是如下四种:

  1. 反向探测技术:扫描IP是不是开通了80,8080等代理服务器经常开通的端口,显然一个普通的用户IP不太可能开通如上的端口。
  2. HTTP头部的X_Forwarded_For:开通了HTTP代理的IP可以通过此法来识别是不是代理IP;如果带有XFF信息,该IP是代理IP无疑。
  3. Keep-alive报文:如果带有Proxy-Connection的Keep-alive报文,该IP毫无疑问是代理IP。
  4. 查看IP上端口:如果一个IP有的端口大于10000,那么该IP大多也存在问题,普通的家庭IP开这么大的端口几乎是不可能的。

以上代理IP检测的方法几乎都是公开的,但是盲目去扫描全网的IP,被拦截不说,效率也是一个很大的问题。

因此,我们的除了利用网络爬虫爬取代理IP外,还利用如下办法来加快代理IP的收集:通过业务建模,收集恶意IP(黑产使用代理IP的可能性比较大)然后再通过协议扫描的方式来判断这些IP是不是代理IP。每天腾讯都能发现千万级别的恶意IP,其中大部分还是代理IP。

二、腾讯用户画像类别概览

大数据架构

三、防御逻辑

大数据架构

实时系统使用C/C++开发实现,所有的数据通过共享内存的方式进行存储,相比其他的系统,安全系统更有他自己特殊的情况,因此这里我们可以使用“有损”的思路来实现,大大降低了开发成本和难度。

数据一致性,多台机器,使用共享内存,如何保障数据一致性?

其实,安全策略不需要做到强数据一致性。

从安全本身的角度看,风险本身就是一个概率值,不确定,所以有一点数据不一致,不影响全局。

但是安全系统也有自己的特点,安全系统一般突发流量比较大,我们这里就需要设置各种应急开关,而且需要微信号、短信等方式方便快速切换,避免将影响扩散到后端系统。

四、接入系统

大数据架构

大数据架构

适应的场景包括:

  • 电商o2o刷单、刷券、刷红包
  • 防止虚假账号注册
  • 防止用户名、密码被撞库
  • 防止恶意登录

Q&A

Q:风险学习引擎是自研的,还是使用的开源库?

风险学习引擎包括两个部分,线上和线下两部分:

  • 线上:自己利用c/c++来实现。
  • 线下:涉及利用python开源库来做的,主要是一些通用算法的训练和调优。

Q:请问魔方平台中用到的MongDB是不是经过改造?因为MongDB一直不被看好,出现问题也比较多。

我们做了部分改造,主要是DB的引擎方面。

Q:请问黑分类器和白分类器有什么区别?

白分类器主要用来识别正常用户,黑分类器识别虚假用户。

Q:风险概率的权重指标是如何考虑的?

先通过正负样本进行训练,并且做参数显著性检查;然后,人工会抽查一些参数的权重,看看跟经验是否相符。

Q:安全跟风控职责如何区分呢?

相比安全,风控的外延更丰富,更注重宏观全局;针对一个公司来讲,风控是包括安全、法务、公关、媒体、客服等在内一整套应急处理预案。

Q:如果识别错了,误伤了正常用户会造成什么后果么?比如影响单次操作还是会一直失败。

如果识别错了正常用户不会被误伤,但是会导致体验多加了一个环节,如弹出验证码、或者人工客服核对等。

想和群内专家继续交流有关高可用架构的问题,请关注公众号后,回复arch,申请进群。

来自《科学农夫》

End.

mac下无法远程桌面连接win10的解决办法

原来在Mac OSX下远程win7系统很正常,后来把windows系统升级成了win10,再远程桌面时就一直失败,提示“远程桌面连接无法验证您希望连接的计算机的身份”。

解决办法:
1,先查看防火墙是否打开,是否去掉“仅允许运行使用网络级别身份验证的远程计算机…”
2,如果还是不行的话。便需要开启组策略中远程桌面链接安全层:
(1)开始-运行-gpedit.msc,进入组策略编辑器。
(2)在左侧边栏中展开,计算机配置-管理模板-Windows组件-远程桌面服务-远程桌面会话主机-安全,修改以下两项。
第一,远程(RDP)连接要求使用指定的安全层,改为启用,安全层选择RDP。
第二,要求使用网络级别的身份验证对远程连接的用户进行身份验证,改为禁用。
(3)关闭组策略编辑器,重启即可。
原文出自:www.hangge.com  转载请保留原文链接:http://www.hangge.com/blog/cache/detail_899.html

10亿红包从天而降,揭秘微信摇一摇背后的技术细节

2016年春节的三波红包雨——摇红包已经过去一段时间,面对亿级的企业资金以及亿级的红包,回想这其中的过程并不简单,充满各种变数,稍有闪失就可能功亏一篑。让我们一起回顾一下这里的准备过程,看看技术上是如何为这只许成功不许失败的项目保驾护航的。

1
除夕活动红包系统

红包系统由三部分组成:信息流、业务流和资金流。这三部分在组织架构上由不同的后台团队完成:信息流——微信后台,业务流——微信支付后台,资金流——财付通后台。

在平时,红包系统主要处理个人会话中以消息形式发出的红包。其中,信息流主要包括用户操作背后的请求通信和红包消息在不同用户和群中的流转;业务流是用户请求引发的包红包、抢红包和拆红包等的业务逻辑;资金流则是红包背后的资金转账和入账等流程。

红包系统在除夕活动时和平时的实现不大一样。在除夕活动时,除了个人红包外,红包系统还要处理由后台通过摇一摇集中下发的大量企业红包。这里边信息流的实现变化较大。

接下来简单介绍一下2016年除夕活动时的红包系统架构,包括三个方面:资源预下载、摇红包、拆红包。

1 资源预下载

在除夕,用户通过摇一摇参与活动,可以摇到红包或其他活动页,这些页面需要用到很多图片、视频或H5页面等资源。在活动期间,参与用户多,对资源的请求量很大,如果都通过实时在线访问,服务器的网络带宽会面临巨大压力,基本无法支撑;另外,资源的尺寸比较大,下载到手机需要较长时间,用户体验也会很差。因此,我们采用预先下载的方式,在活动开始前几天把资源推送给客户端,客户端在需要使用时直接从本地加载。

2 摇/拆红包

除夕的摇一摇子系统是专门为活动定制的,按时间轴进行各项活动,这里边最重要、同时也是请求量最大的是摇红包。从需求上看,系统需要完成两个事:用户可以通过摇一摇抢到红包,红包金额可以入到用户的支付账户。在除夕,系统需要在很短时间内将几十亿个红包发放下去,对性能和可用性要求很高。

考虑到涉及资金的业务逻辑比较复杂,还有很多数据库事务处理,耗时会比较长,于是我们将抢红包(信息流)和红包的账务逻辑(业务流和资金流)异步化。将前一部分处理流程尽可能设计得轻量,让用户可以很快抢到红包,然后再异步完成剩下的账务逻辑。

那么,抢红包阶段是怎样做到既轻量又可靠呢?

1)零RPC调用 

在微信后台系统中,一般情况下客户端发起的请求都是通过接入服务转发给具体的业务服务处理的,会产生RPC调用。但对于摇一摇请求,我们将摇一摇逻辑直接嵌入接入服务中,接入服务可以直接处理摇一摇请求,派发红包。

2)零数据库存储 

按一般的系统实现,用户看到的红包在系统中是数据库中的数据记录,抢红包就是找出可用的红包记录,将该记录标识为属于某个用户。在这种实现里,数据库是系统的瓶颈和主要成本开销。我们在这一过程完全不使用数据库,可以达到几个数量级的性能提升,同时可靠性有了更好的保障。

  • 支付系统将所有需要下发的红包生成红包票据文件;
  • 将红包票据文件拆分后放到每一个接入服务实例中;
  • 接收到客户端发起摇一摇请求后,接入服务里的摇一摇逻辑拿出一个红包票据,在本地生成一个跟用户绑定的加密票据,下发给客户端;
  • 客户端拿加密票据到后台拆红包,后台的红包简化服务通过本地计算即可验证红包,完成抢红包过程。

3)异步化 

用户抢到红包后不会同步进行后续的账务处理,请求会被放入红包异步队列,再通过异步队列转给微信支付后台,由微信支付后台完成后续业务逻辑。

2
大规模集群中保证数据一致性

事实上网络分裂很难从根本上避免,我们在设计系统时都是假设网络分裂一定会出现,基于此思考应该采用什么方案保障系统在网络分裂时能正常运作。

我们的方案是在每个数据中心都建设三个独立的数据园区,可以做到在任意一个数据园区出现网络分裂等故障,甚至彻底变成园区孤岛后,另外两个数据园区可以无损承接整个数据中心的请求。

三园区容灾的关键就是数据一致性。我们需要做到在分裂出去的那个数据园区的数据在其他园区有个强一致的副本,这样请求落到其他两个园区后,可以无损完成服务,此外在故障园区恢复后,数据在所有园区还能自动保持强一致。微信后台实现了基于Quorum算法,对数据有强一致性保证的存储系统——KvSvr(这一存储系统另文介绍)。

此外还有可以提供三园区强一致保证的可靠异步队列,这次就应用在这个红包系统中。前边提到的部署在接入服务的红包文件实际上也是可以实现三园区容灾的,我们在每台接入服务部署的红包文件都会在其他数据园区有个备份。在某个数据园区故障时,我们可以在其他数据园区发放故障园区的红包。

今年活动红包总量非常大,活动形式也更丰富,我们在以下方面做了优化。

1 服务性能

为提升各个服务模块的处理性能,我们通过压测和Profiler分析,发现了不少性能瓶颈点,做了大量优化。

2 业务支撑能力

支持更加复杂的业务场景,并在客户端和服务器都加入了很多可以后期灵活调整的预埋能力,以更好地服务产品运营。

3 可用性

不断提升系统可用性是我们一直努力的目标,以下5点很好地提高了系统的可用性。

1)系统容量评估与配额 

对系统的容量需要有个准确的评估与验证,并结合业务设计合理的配额方案和降级方案,尽可能保障系统不会过载。例如,我们评估并验证完系统每秒最大拆红包量后,就可以在处理用户摇一摇请求时,限制系统每秒最大发放红包配额,这就间接保证了拆红包量不会超出处理能力。

2)过载保护 

服务如果出现过载了,必须有能力自保,不被压垮,并且不扩散到系统其他的服务。我们在后台的服务框架层面具备通用的过载保护能力:服务如果处理不过来,就按请求的优先级尽快丢掉超出处理能力的请求,保证服务的有效输出;上游调用端在部分服务实例过载时,能自动做负载均衡调整,将请求调整到负载较低的服务实例中;上游调用端发现大部分服务实例都出现过载,也可以主动丢掉部分请求,减轻后端服务器的负担。

3)减少关键路径 

减少核心用户体验所涉及的步骤和模块,集中力量保证关键路径的可用性,从而在整体上提高可用性。我们把活动红包的信息流和业务流进行异步化,就是基于这个考虑。跟用户核心体验相关的抢红包操作,在信息流中的接入服务、红包简化逻辑服务和红包异步队列(入队)这三个服务模块参与下即可完成。这三个服务模块是可以比较容易用较低成本就做到高可用的,可以较好地规避业务流和资金流中几十甚至上百个服务模块可能出现的风险。

4)监控指标 

我们需要对系统的真实负载情况有准确及时的了解,就必须要有一套高效、可靠的监控系统,同时还要有一套有效的监控指标,监控指标不是越多越好,太多了反而会影响判断,必须要有能准确反映问题的几个核心指标。在我们系统里,这些核心指标一般在基础框架集成,根据经验来看,其中一个很有用的指标是服务的最终系统失败。

我们把服务的失败分为两类:逻辑失败和系统失败。系统失败一般是服务暂时不可用导致,是可通过重试来自动解决的,如果请求重试若干次仍然为系统失败,就产生最终系统失败。通过最终系统失败通常可以快速定位到异常的服务,及时进行处置。

5)人工介入 

我们在红包系统内预置了很多配置开关,当自动运作的过载保护无法发挥预期作用时,可以通过人工介入,使用这些保底的手动开关迅速降低负载、恢复服务。

3
技术创新

实际上,类似的这种活动用到的技术都是现成的,并不复杂。但为什么大家会觉得很难实现呢?主要是因为规模:用户和请求的并发规模越大,就越难在系统的成本和可用性上达到平衡,也就是说越难实现一个低运营成本、高服务可用性的系统。

在传统的应对这种有很大规模用户参与的活动的实现方案里,一般会采用在客户端过滤请求,以某种概率(基于时间或互动次数)发到服务器进行处理,这样服务器的压力可以大幅降低。

我们认为还可以做得更好,在这种活动的技术方案上可以有所突破——在保持低成本的前提下,全量处理用户的每次交互。这就大大降低了客户端的实现风险(因为客户端的更新和覆盖周期相对较长)。此外,服务器有了对用户交互有了全面的控制能力和灵活调整的能力。

这些能力对活动的运营是非常可贵的。可以让我们在活动过程中,各种复杂用户场景下,都能做到精细的调整,给了产品运营很大的灵活性,以保证活动效果和用户体验。看看下面两个例子。

  •  我们可以精确控制和调整每次用户交互出现什么结果,曝光哪个赞助商;
  • 活动过程中,有个很难预估的因素——参与人数。说不准参与的用户少了(或互动次数少了),导致红包很久都发不完?或者参与的用户多了(或互动次数多了),导致需要加快发放速度,更快速发完红包?

于是我们对这个技术方案做了全面的思考和设计,最终实现了现在这个系统,可以用很低的成本实现极高的性能和可用性,在除夕活动中得到了成功的应用。

4
服务降级方案

我们对摇一摇/朋友圈红包照片在2016.1.26的预热活动和2016.2.7的正式活动都做了详细的复盘。包括活动过程中各项业务数据是否符合预期,各个模块的表现是否符合预期等,并分析各种不符合预期表现的成因和解决措施。

在红包系统的信息流、业务流和资金流都有很多保障用户核心体验的降级方案。举几个信息流的降级方案的例子。

a) 如果某一个数据园区出现网络分裂等故障,完全不可用了,部署在那里的红包怎么发下去? 

红包文件虽然在园区间有冗余存储,但基于性能和可用性考虑,我们并不打算在各园区间维护强一致的红包发放记录,做到记录级的“断点续发”,而是将红包文件按时段进行切分,降级为只做文件级的“断点续发”。在某个园区不可用时,使用降级方案后,故障园区当前发放时段的红包文件不会接着发放,仅保证下一时段的红包能通过其他园区正常发出去。

b)活动过程中,如果用户的交互量超过服务的处理能力了怎么办?

正如前面所述,我们很难准确估计参与用户量及互动次数。就本次活动而言,在系统设计之初,我们评估有2000万次/秒的峰值请求,并在系统最终实现和部署时预留了一定的余量,提供了评估值2.5倍(也就是5000万次/秒峰值请求)的系统处理能力,除夕当晚服务器处理请求峰值达到2500万次/秒,服务实际负载低于50%。但如果当时用户过多,互动过于火爆,到达后台的请求超过5000万次/秒,系统就会进入降级模式,客户端可以在服务器的控制下,减少请求,防止服务器过载。

c)红包发放速度过快,后端处理不过来怎么办? 

正如前面所述,用户抢红包是在信息流完成的,完成后请求放到红包异步队列再转到业务流。如果领取红包请求量过大,队列会出现积压,红包的入账会出现延时,但不会导致用户请求失败。

类似的降级方案还有很多,每个环节都有若干个不同的降级方案,有些是针对业务专门设计的(如a和b),还有些是使用基础组件/服务/方案本身就具备的降级能力(如c)。这些方案都遵循着一个原则:降级时,尽可能保证用户的核心体验。

5
资金与红包准备的难点

除夕活动资金和红包准备的难点总体来说有以下4点。

1)红包的数量 

  • 由于招商的不确定性,最终投入微信摇企业红包的资金不可准确预估;
  • 不同的商户其对品牌的曝光量的需求不同,有的要求多曝光,有的要求多关注,红包金额或大或小,数量则或多或少;
  • 从准备到除夕当天,期间各种变化,活动方案变数很多。

红包的数量可能从几亿到几百亿变化,资金与红包的准备需要能够满足需求的巨大变化。

2)资金的就位 

  • 企业各行各业,营销经费的审批流程不尽相同,资金最终支付到位时间前后差异很大,甚至有些在除夕前一周才会最终敲定支付到账;
  • 某些企业可能在最后阶段停止合作;
  • 某些企业可能在最后阶段调整资金的使用方式。

以上情况会导致资金的到位时间不完全可控,本着尽可能多的资金能够投入到活动中的原则,希望可以尽可能压缩活动的准备时间,让更多的资金能够上车。

3)资金预算的配置方案(资金剧本) 

  • 按设想的活动方案,红包会分为三大类:图片红包、视频红包、品牌logo红包。活动方案较去年又有新的变化,特别是logo红包的认知度和接受度不确定,logo红包过度会否导致未领完而浪费资金,不容易确定logo红包的资金占比;
  • 除夕当天的活动剧本可能会反复调整优化,红包时段的划分可能修改,不同时段的资金投放量可能变化。

大笔资金、大量的红包、复杂的活动方案、善变的商户要求,都可能反复调整,如果面对百亿量级的红包配置调整时,技术上如何实现缩短准备时间,支持方便的变化。

4)资金的安全 

  • 如何防止红包的金额被篡改;
  • 如何防止未被领取的红包被入账给用户;
  • 如何防止红包金额重复入账给用户;
  • 如何防止机器被攻破产生了不存在的红包;
  • 如何防止红包被不同用户重复领取;
  • 如何在确保资金安全的情况下尽可能保证用户的到账体验(最好是实时或准实时到账)。

技术上必须做万无一失的准备,确保资金足够安全,活动顺利完成。

6
微信摇企业红包全过程

如果在除夕当天摇的过程中按前边提到的超级复杂的配置方案即时生成随机红包,这显然是风险齐高逻辑奇复杂的。对待只许成功不许失败的项目,主流程必须极简高效,所以这里全部的资金和红包数量都需要按方案规则提前切分和准备好。

将预生成好的红包数据(预红包数据)进行准确的部署,摇红包的资金和红包准备的整体流程方案有两个选择。

  • 方案一:预红包数据提供部署给微信的接入机和写入红包DB,摇红包过程由红包接入机控制红包的发放,拆红包时修改红包DB中的红包数据;
  • 方案二:预红包数据只提供部署给微信接入机,摇红包过程由红包接入机控制红包的发放,拆红包直接Insert到红包DB。

第二个方案减少一次DB操作,如果是百亿量级的红包数据,可以极大减少数据导入、对账等活动准备时间,特别是方案需要变更时。

7
充足准备

1 对资金预算和资金剧本进行合理的建模

首先,面对如此大量的资金和复杂的资金剧本,如何准确高效地管理和控制逻辑呢?要杜绝傻大黑粗,我们需要一个优雅的解决方案——建模。

对资金预算和资金剧本进行合理的建模,让模型涵盖、掌管、控制一切——让工具基于模型进行自动化的处理和校验,这里需要做的就是:

  • 落地该模型的设计,让一切围着模型转;
  • 按规定的格式文件导入预算和配置,并进行数据和逻辑合理性校验;
  • 一切利用工具进行处理,资金的支付、退款、红包的随机生成和多商户随机打散、预红包文件分割、预红包数据的校验,减少人为过程导致的潜在失误;
  • 优化红包随机算法和文件处理方法,将红包随机分割和多商户随机打散算法的n^2时间复杂度优化n,压测30亿红包的生成时间为2~3小时,极大缩减准备时间,增加方案调整的回旋时间,可以让更多的资金上车。

其次,前边提到方案有如此多的变数、调整、变更,如何支持?还是回归模型,建模时就要考虑支持同样的预算多套资金配置方案。

同样的资金预算,同时或依次生成多套预红包数据文件,提前做多手准备,方便方案变更。

再次,如此大量的资金就意味着如此大量的诱惑,会不会出问题呢?

  • 方案预红包数据未提前落地DB,导致拆红包时缺少一次红包数据有效性的检验;
  • 预红包数据存放在微信接入机上,存在被攻陷获取或篡改的可能;
  • 红包数据在传输的过程中存在系统异常或恶意攻击,导致数据错误特别是金额错误的可能;
  • 系统内部可能存在恶意人员直接调用拆红包的接口写入不存在的红包。

墨菲定律要求我们必须重视以上安全隐患,解决方案就是加密——对预红包数据进行加密,加密库和解密库独立维护保证密钥不被泄漏,通过工具生成预红包数据时用密钥进行加密,预红包数据在部署存储和传输的过程中保持加密状态,仅在拆红包的逻辑中编译二进制紧密库进行解密。

同时,鸡蛋也不能放在一个篮子里,为了控制密钥泄漏导致的影响,确保资金风险可控,整个预生成的红包数据可能分别使用了几百到几千个密钥进行加密,一个密钥加密的红包资金量在20~30万。解密库还需要能设置密钥ID的白名单和黑名单,确保未被使用的密钥不能被利用,确认泄漏的密钥可以进行拦截。

2 极限压缩

如果是百亿个红包,那么产生预红包数据文件的大小不经过压缩是非常恐怖的,传输部署几百GB或几TB的数据是很艰苦的,一旦发生调整变更会变得非常艰难,所以需要对数据进行极限的压缩。

  • 对于支付单号、商户号、红包账户等信息,由工具导成配置文件,配置到拆红包逻辑中,加密的红包数据中仅用一个批次ID表达;
  • 拆分红包ID,部分分段同样转为ID,解密库解密后利用配置进行还原;
  • 加密部分(Ticket):红包ID、金额、批次ID、密钥ID,压缩到16字节;
  • 单条红包记录二进制表达,压缩到26字节。

3 对账

上面所有都做到就安全了么?真的就有人写了一个不存在红包进来会怎样?是否还有其他未考虑到的潜在风险?所以我们需要一个兜底——对账,把一切都要对清楚才放心。

对账后再入账的时效在30~60分钟,会造成不好的用户体验。为了提升用户体验,将大额的预红包数据(占比10%左右)导入KV(高速缓存),拆红包时进行即时校验,即时进行转账入账,未命中KV的红包则等待对账后异步完成入账。

  • 资金配置与资金预算要总分对账;
  • 红包数据文件与资金剧本进行总分对账;
  • 红包数据进行全局去重验证;
  • 红包数据进行解密验证和金额验证;
  • 如果密钥泄漏红包金额等被篡改,兜底要进行红包DB中已拆红包数据与预红包数据的对账后,才能实际进行转账入账。
8
小结

未来我们可能还继续会有类似的活动,虽然我们已经有了一个经过实践的可复用的技术方案,接下来我们希望能够继续优化,并实现一些可复用的模块和服务,可以快速应用到下一次活动或者其他业务场景中。例如,我们在2015年春节首次完成除夕活动后,就把其中的资源预下载系统进行了完善和通用化,随后应用在多个业务场景和这次的2016年除夕活动中。未来可以有更多类似的系统和服务可以被复用和通用化。

老司机介绍
张文瑞 一直从事后台系统设计开发,早期涉足传统行业软件,后投身互联网。作为微信最早的后台开发者之一,见证了微信从零开始到逐渐发展壮大的过程。
王鹏程 微信支付商户系统开发组组长,专家工程师。2008年加入腾讯,6年的电商经验,做过c2c、b2b2c、b2c及erp,2年第三方支付开发经验。

[数据库规范]初版

通常来讲,各个互联网公司的数据库分为5个数据库环境:

1. dev : 开发环境, 开发可读写,可修改表结构;  开发人员可以修改表结构, 可 以随意修改其中的数据; 但是需要保证不影响其他开发同事;

2. qa : 测试环境, 开发可读写, 开发人员可以通过工具修改表结构;

3. sim: 模拟环境, 开发可读写, 通过web平台;发起上线请求时,会先在这个环境 上进行预执行, 这个环境也可供部署上线演练或压力测试使用 可以读写;

4. real: 生产数据库从库(准实时同步),只读环境,不允许修改数据,不允许修改 表结构; 供线上问题查找,数据查询等使用;

5. online: 线上环境;开发人员不允许直接在线上环境进行数据库操作,如果需要 操作必须找DBA进行操作并进行相应记录;

这些环境的机器,一定要做到权限划分明确,读写帐号分离,并且有辨识度,能区 分具体业务。例如用户名w_wap, r_wap 能看出来,读写帐号是wap应用的。

[数据库命名规范]

1. 尽量简洁明义,能够一眼看出来这个数据库是用来做什么的;
2. 使用名词作为数据库名称,并且只用英文,不用中文拼音;
3. 使用英文字母,全部小写,控制在3-7个字母以内;
4. 如果有多个单词,则使用下划线隔开,不建义驼峰命名;
例如,每个公司都有crm业务,那就叫做xx_crm, 字符集统一utf8。字符集踩过的 坑很多,为了通用性统一utf8。

create database xx_crm default character set=utf8;

[表命名规范]

1. 具备统一前缀,对相关功能的表应当使用相同前缀,如 acl_xxx,house_xxx,ppc_xxx;其中前缀通常为这个表的模块或依赖主实体对象的 名字,通常来讲表名为:业务_动作_类型,或是业务_类型;
2. 表名使用英文小写单词,如果有多个单词则使用下划线隔开;
3.表名简介,使用常见单词,避免使用长单词和生僻词;
4. 表引擎取决于实际应用场景及当前数据库中的已经存在的存储引擎;日志及报 表类表建议用myisam,与交易,审核,金额相关的表建议用innodb引擎。 总体来 讲数据库默认innodb;
5. 数据表必须有主键,且建议均使用auto_increment的id作为主键(与业务无 关),和业务相关的要做为唯一索引;
6. 默认使用utf8字符集(由于数据库定义使用了默认,数据表可以不再定义,但 为保险起见,建议都写上);
7. 所有的表都必须有备注,写明白这个表中存放的数据内容;
8. 预估表数据量,如果数据量较大(超过500w)则需要考虑分表策略。可以等量 均衡分表或根据业务规则分表均可。要分表的数据表必须与DBA商量分表策略;
9. 职责相近的表,命名规则应该相同;如合同申请,账户信息,交友相关等;
举个例子,一张在线冲值记录表:user_bank_deposit 这个就非常符合标准,如果 叫做userBankDeposit或是user_chongzhi,就非常不友好。

[字段命名规范]

1. 数据库字段命名与表名命名类似:
2. 使用小写英文单词,如果有多个单词使用下划线隔开;
3. 使用简单单词,避免生僻词;
4. 字段应当有注释,描述该字段的用途及可能存储的内容,如枚举值则建议将该 字段中使用的内容都定义出来;
5. 是别的表的外键均使用xxx_id的方式来表明;
6. 表的主键一般都约定成为id,自增类型;
7. 时间字段,除特殊情况一律采用int来记录unix_timestamp;
8. 网络IP字段,除特殊情况一律用bigint来记录inet_aton值;
9. 所有字段,均为非空,最好显示指定默认值;
10. 有些驱动对tinyint支持不够好,通常建义按容量来选择字段;
11. text字段尽量少用,或是拆到冗余表中;

Linux Shell 文本处理工具集锦

文将介绍Linux下使用Shell处理文本时最常用的工具:

find、grep、xargs、sort、uniq、tr、cut、paste、wc、sed、awk;

提供的例子和参数都是最常用和最为实用的;

我对shell脚本使用的原则是命令单行书写,尽量不要超过2行;

如果有更为复杂的任务需求,还是考虑python吧;

 find 文件查找

  • 查找txt和pdf文件
      find . \( -name "*.txt" -o -name "*.pdf" \) -print
  • 正则方式查找.txt和pdf
      find . -regex  ".*\(\.txt|\.pdf\)$"

    -iregex: 忽略大小写的正则

  • 否定参数
    查找所有非txt文本

       find . ! -name "*.txt" -print
  • 指定搜索深度
    打印出当前目录的文件(深度为1)

      find . -maxdepth 1 -type f

  定制搜索

  • 按类型搜索:
      find . -type d -print  //只列出所有目录

    -type f 文件 / l 符号链接

  • 按时间搜索:
    -atime 访问时间 (单位是天,分钟单位则是-amin,以下类似)
    -mtime 修改时间 (内容被修改)
    -ctime 变化时间 (元数据或权限变化)
    最近7天被访问过的所有文件:

      find . -atime 7 -type f -print
  • 按大小搜索:
    w字 k M G
    寻找大于2k的文件

      find . -type f -size +2k

    按权限查找:

      find . -type f -perm 644 -print //找具有可执行权限的所有文件

    按用户查找:

      find . -type f -user weber -print// 找用户weber所拥有的文件

  找到后的后续动作

  • 删除:
    删除当前目录下所有的swp文件:

      find . -type f -name "*.swp" -delete
  • 执行动作(强大的exec)
      find . -type f -user root -exec chown weber {} \; //将当前目录下的所有权变更为weber

    注:{}是一个特殊的字符串,对于每一个匹配的文件,{}会被替换成相应的文件名;
    eg:将找到的文件全都copy到另一个目录:

      find . -type f -mtime +10 -name "*.txt" -exec cp {} OLD \;
  • 结合多个命令
    tips: 如果需要后续执行多个命令,可以将多个命令写成一个脚本。然后 -exec 调用时执行脚本即可;

      -exec ./commands.sh {} \;

  -print的定界符

默认使用’\n’作为文件的定界符;

-print0 使用’\0’作为文件的定界符,这样就可以搜索包含空格的文件;

 grep 文本搜索

grep match_patten file // 默认访问匹配行

  • 常用参数
    -o 只输出匹配的文本行 VS -v 只输出没有匹配的文本行
    -c 统计文件中包含文本的次数

      grep -c "text" filename

    -n 打印匹配的行号
    -i 搜索时忽略大小写
    -l 只打印文件名

  • 在多级目录中对文本递归搜索(程序员搜代码的最爱):
      grep "class" . -R -n
  • 匹配多个模式
      grep -e "class" -e "vitural" file
  • grep输出以\0作为结尾符的文件名:(-z)
      grep "test" file* -lZ| xargs -0 rm

 xargs 命令行参数转换

xargs 能够将输入数据转化为特定命令的命令行参数;这样,可以配合很多命令来组合使用。比如grep,比如find;

  • 将多行输出转化为单行输出
    cat file.txt| xargs
    \n 是多行文本间的定界符
  • 将单行转化为多行输出
    cat single.txt | xargs -n 3
    -n:指定每行显示的字段数

  xargs参数说明

-d 定义定界符 (默认为空格 多行的定界符为 \n)

-n 指定输出为多行

-I {} 指定替换字符串,这个字符串在xargs扩展时会被替换掉,用于待执行的命令需要多个参数时

eg:

cat file.txt | xargs -I {} ./command.sh -p {} -1

-0:指定\0为输入定界符

eg:统计程序行数

find source_dir/ -type f -name "*.cpp" -print0 |xargs -0 wc -l

 sort 排序

字段说明:

-n 按数字进行排序 VS -d 按字典序进行排序

-r 逆序排序

-k N 指定按第N列排序

eg:

sort -nrk 1 data.txt
sort -bd data // 忽略像空格之类的前导空白字符

 uniq 消除重复行

  • 消除重复行
      sort unsort.txt | uniq
  • 统计各行在文件中出现的次数
      sort unsort.txt | uniq -c
  • 找出重复行
      sort unsort.txt | uniq -d

    可指定每行中需要比较的重复内容:-s 开始位置 -w 比较字符数

 用tr进行转换

  • 通用用法
      echo 12345 | tr '0-9' '9876543210' //加解密转换,替换对应字符
      cat text| tr '\t' ' '  //制表符转空格
  • tr删除字符
      cat file | tr -d '0-9' // 删除所有数字

    -c 求补集

      cat file | tr -c '0-9' //获取文件中所有数字
      cat file | tr -d -c '0-9 \n'  //删除非数字数据
  • tr压缩字符
    tr -s 压缩文本中出现的重复字符;最常用于压缩多余的空格

      cat file | tr -s ' '
  • 字符类
    tr中可用各种字符类:
    alnum:字母和数字
    alpha:字母
    digit:数字
    space:空白字符
    lower:小写
    upper:大写
    cntrl:控制(非可打印)字符
    print:可打印字符
    使用方法:tr [:class:] [:class:]

      eg: tr '[:lower:]' '[:upper:]'

 cut 按列切分文本

  • 截取文件的第2列和第4列:
      cut -f2,4 filename
  • 去文件除第3列的所有列:
      cut -f3 --complement filename
  • -d 指定定界符:
      cat -f2 -d";" filename
  • cut 取的范围
    N- 第N个字段到结尾
    -M 第1个字段为M
    N-M N到M个字段
  • cut 取的单位
    -b 以字节为单位
    -c 以字符为单位
    -f 以字段为单位(使用定界符)
  • eg:
      cut -c1-5 file //打印第一到5个字符
      cut -c-2 file  //打印前2个字符

 paste 按列拼接文本

将两个文本按列拼接到一起;

cat file1
1
2

cat file2
colin
book

paste file1 file2
1 colin
2 book

默认的定界符是制表符,可以用-d指明定界符

paste file1 file2 -d ","
1,colin
2,book

 wc 统计行和字符的工具

wc -l file // 统计行数

wc -w file // 统计单词数

wc -c file // 统计字符数

 sed 文本替换利器

  • 首处替换
      seg 's/text/replace_text/' file   //替换每一行的第一处匹配的text
  • 全局替换
       seg 's/text/replace_text/g' file

    默认替换后,输出替换后的内容,如果需要直接替换原文件,使用-i:

      seg -i 's/text/repalce_text/g' file
  • 移除空白行:
      sed '/^$/d' file
  • 变量转换
    已匹配的字符串通过标记&来引用.

    echo this is en example | seg 's/\w+/[&]/g'
    $>[this]  [is] [en] [example]
  • 子串匹配标记
    第一个匹配的括号内容使用标记 \1 来引用

      sed 's/hello\([0-9]\)/\1/'
  • 双引号求值
    sed通常用单引号来引用;也可使用双引号,使用双引号后,双引号会对表达式求值:

      sed 's/$var/HLLOE/'

    当使用双引号时,我们可以在sed样式和替换字符串中指定变量;

    eg:
    p=patten
    r=replaced
    echo "line con a patten" | sed "s/$p/$r/g"
    $>line con a replaced
  • 其它示例
    字符串插入字符:将文本中每行内容(PEKSHA) 转换为 PEK/SHA

      sed 's/^.\{3\}/&\//g' file

 awk 数据流处理工具

  • awk脚本结构
    awk ‘ BEGIN{ statements } statements2 END{ statements } ‘
  • 工作方式
    1.执行begin中语句块;
    2.从文件或stdin中读入一行,然后执行statements2,重复这个过程,直到文件全部被读取完毕;
    3.执行end语句块;
  • 使用不带参数的print时,会打印当前行;
      echo -e "line1\nline2" | awk 'BEGIN{print "start"} {print } END{ print "End" }'
  • print 以逗号分割时,参数以空格定界;
    echo | awk ' {var1 = "v1" ; var2 = "V2"; var3="v3"; \
    print var1, var2 , var3; }'
    $>v1 V2 v3
  • 使用-拼接符的方式(””作为拼接符);
    echo | awk ' {var1 = "v1" ; var2 = "V2"; var3="v3"; \
    print var1"-"var2"-"var3; }'
    $>v1-V2-v3

  特殊变量: NR NF $0 $1 $2

NR:表示记录数量,在执行过程中对应当前行号;
NF:表示字段数量,在执行过程总对应当前行的字段数;
$0:这个变量包含执行过程中当前行的文本内容;
$1:第一个字段的文本内容;
$2:第二个字段的文本内容;

echo -e "line1 f2 f3\n line2 \n line 3" | awk '{print NR":"$0"-"$1"-"$2}'
  • 打印每一行的第二和第三个字段:
      awk '{print $2, $3}' file
  • 统计文件的行数:
      awk ' END {print NR}' file
  • 累加每一行的第一个字段:
      echo -e "1\n 2\n 3\n 4\n" | awk 'BEGIN{num = 0 ;
      print "begin";} {sum += $1;} END {print "=="; print sum }'

  传递外部变量

var=1000
echo | awk '{print vara}' vara=$var #  输入来自stdin
awk '{print vara}' vara=$var file # 输入来自文件

  用样式对awk处理的行进行过滤

awk ‘NR < 5’ #行号小于5
awk ‘NR==1,NR==4 {print}’ file #行号等于1和4的打印出来
awk ‘/linux/’ #包含linux文本的行(可以用正则表达式来指定,超级强大)
awk ‘!/linux/’ #不包含linux文本的行

  设置定界符

使用-F来设置定界符(默认为空格)
awk -F: ‘{print $NF}’ /etc/passwd

  读取命令输出

使用getline,将外部shell命令的输出读入到变量cmdout中;

echo | awk '{"grep root /etc/passwd" | getline cmdout; print cmdout }'

  在awk中使用循环

for(i=0;i<10;i++){print $i;}
for(i in array){print array[i];}

eg:
以逆序的形式打印行:(tac命令的实现)

seq 9| \
awk '{lifo[NR] = $0; lno=NR} \
END{ for(;lno>-1;lno--){print lifo[lno];}
} '

  awk实现head、tail命令

  • head:
      awk 'NR<=10{print}' filename
  • tail:
      awk '{buffer[NR%10] = $0;} END{for(i=0;i<11;i++){ \
      print buffer[i %10]} } ' filename

  打印指定列

  • awk方式实现:
      ls -lrt | awk '{print $6}'
  • cut方式实现
      ls -lrt | cut -f6

  打印指定文本区域

  • 确定行号
      seq 100| awk 'NR==4,NR==6{print}'
  • 确定文本
    打印处于start_pattern 和end_pattern之间的文本;

      awk '/start_pattern/, /end_pattern/' filename

    eg:

    seq 100 | awk '/13/,/15/'
    cat /etc/passwd| awk '/mai.*mail/,/news.*news/'

  awk常用内建函数

index(string,search_string):返回search_string在string中出现的位置
sub(regex,replacement_str,string):将正则匹配到的第一处内容替换为replacement_str;
match(regex,string):检查正则表达式是否能够匹配字符串;
length(string):返回字符串长度

echo | awk '{"grep root /etc/passwd" | getline cmdout; print length(cmdout) }'

printf 类似c语言中的printf,对输出进行格式化
eg:

seq 10 | awk '{printf "->%4s\n", $1}'

 迭代文件中的行、单词和字符

  1. 迭代文件中的每一行

  • while 循环法
    while read line;
    do
    echo $line;
    done < file.txt
    改成子shell:
    cat file.txt | (while read line;do echo $line;done)
  • awk法:
    cat file.txt| awk ‘{print}’

  2.迭代一行中的每一个单词

for word in $line;
do 
echo $word;
done

  3. 迭代每一个字符

${string:start_pos:num_of_chars}:从字符串中提取一个字符;(bash文本切片)
${#word}:返回变量word的长度

for((i=0;i<${#word};i++))
do
echo ${word:i:1);
done

本文为《linux Shell脚本攻略》的读书笔记

golang中并发sync和channel

golang中实现并发非常简单,只需在需要并发的函数前面添加关键字"go”,但是如何处理go并发机制中不同goroutine之间的同步与通信,golang 中提供了sync包和channel机制来解决这一问题.

sync 包提供了互斥锁这类的基本的同步原语.除 Once 和 WaitGroup 之外的类型大多用于底层库的例程。更高级的同步操作通过信道与通信进行。

type Cond
    func NewCond(l Locker) *Cond
    func (c *Cond) Broadcast()
    func (c *Cond) Signal()
    func (c *Cond) Wait()
type Locker
type Mutex
    func (m *Mutex) Lock()
    func (m *Mutex) Unlock()
type Once
    func (o *Once) Do(f func())
type Pool
    func (p *Pool) Get() interface{}
    func (p *Pool) Put(x interface{})
type RWMutex
    func (rw *RWMutex) Lock()
    func (rw *RWMutex) RLock()
    func (rw *RWMutex) RLocker() Locker
    func (rw *RWMutex) RUnlock()
    func (rw *RWMutex) Unlock()
type WaitGroup
    func (wg *WaitGroup) Add(delta int)
    func (wg *WaitGroup) Done()
    func (wg *WaitGroup) Wait()

而golang中的同步是通过sync.WaitGroup来实现的.WaitGroup的功能:它实现了一个类似队列的结构,可以一直向队列中添加任务,当任务完成后便从队列中删除,如果队列中的任务没有完全完成,可以通过Wait()函数来出发阻塞,防止程序继续进行,直到所有的队列任务都完成为止.

WaitGroup总共有三个方法:Add(delta int), Done(), Wait()。

Add:添加或者减少等待goroutine的数量

Done:相当于Add(-1)

Wait:执行阻塞,直到所有的WaitGroup数量变成0

具体例子如下:

package main

import (
	"fmt"
	"sync"
)

var waitgroup sync.WaitGroup

func Afunction(shownum int) {
	fmt.Println(shownum)
	waitgroup.Done() //任务完成,将任务队列中的任务数量-1,其实.Done就是.Add(-1)
}

func main() {
	for i := 0; i < 10; i++ {
		waitgroup.Add(1) //每创建一个goroutine,就把任务队列中任务的数量+1
		go Afunction(i)
	}
	waitgroup.Wait() //.Wait()这里会发生阻塞,直到队列中所有的任务结束就会解除阻塞
}

使用场景:  程序中需要并发,需要创建多个goroutine,并且一定要等这些并发全部完成后才继续接下来的程序执行.WaitGroup的特点是Wait()可以用来阻塞直到队列中的所有任务都完成时才解除阻塞,而不需要sleep一个固定的时间来等待.但是其缺点是无法指定固定的goroutine数目.

Channel机制:

相对sync.WaitGroup而言,golang中利用channel实习同步则简单的多.channel自身可以实现阻塞,其通过<-进行数据传递,channel是golang中一种内置基本类型,对于channel操作只有4种方式:

创建channel(通过make()函数实现,包括无缓存channel和有缓存channel);

向channel中添加数据(channel<-data);

从channel中读取数据(data<-channel);

关闭channel(通过close()函数实现,关闭之后无法再向channel中存数据,但是可以继续从channel中读取数据)

channel分为有缓冲channel和无缓冲channel,两种channel的创建方法如下:

var ch = make(chan int) //无缓冲channel,等同于make(chan int ,0)

var ch = make(chan int,10) //有缓冲channel,缓冲大小是5

其中无缓冲channel在读和写是都会阻塞,而有缓冲channel在向channel中存入数据没有达到channel缓存总数时,可以一直向里面存,直到缓存已满才阻塞.由于阻塞的存在,所以使用channel时特别注意使用方法,防止死锁的产生.例子如下:

无缓存channel:

package main

import "fmt"

func Afuntion(ch chan int) {
	fmt.Println("finish")
	<-ch
}

func main() {
	ch := make(chan int) //无缓冲的channel
	go Afuntion(ch)
	ch <- 1
	
	// 输出结果:
	// finish
}

代码分析:首先创建一个无缓冲channel ch, 然后执行 go Afuntion(ch),此时执行<-ch,则Afuntion这个函数便会阻塞,不再继续往下执行,直到主进程中ch<-1向channel ch 中注入数据才解除Afuntion该协程的阻塞.

package main

import "fmt"

func Afuntion(ch chan int) {
	fmt.Println("finish")
	<-ch
}

func main() {
	ch := make(chan int) //无缓冲的channel
	//只是把这两行的代码顺序对调一下
	ch <- 1
	go Afuntion(ch)

	// 输出结果:
	// 死锁,无结果
}

代码分析:首先创建一个无缓冲的channel, 然后在主协程里面向channel ch 中通过ch<-1命令写入数据,则此时主协程阻塞,就无法执行下面的go Afuntions(ch),自然也就无法解除主协程的阻塞状态,则系统死锁

总结:
对于无缓存的channel,放入channel和从channel中向外面取数据这两个操作不能放在同一个协程中,防止死锁的发生;同时应该先利用go 开一个协程对channel进行操作,此时阻塞该go 协程,然后再在主协程中进行channel的相反操作(与go 协程对channel进行相反的操作),实现go 协程解锁.即必须go协程在前,解锁协程在后.

带缓存channel:
对于带缓存channel,只要channel中缓存不满,则可以一直向 channel中存入数据,直到缓存已满;同理只要channel中缓存不为0,便可以一直从channel中向外取数据,直到channel缓存变为0才会阻塞.

由此可见,相对于不带缓存channel,带缓存channel不易造成死锁,可以同时在一个goroutine中放心使用,

close():
close主要用来关闭channel通道其用法为close(channel),并且实在生产者的地方关闭channel,而不是在消费者的地方关闭.并且关闭channel后,便不可再想channel中继续存入数据,但是可以继续从channel中读取数据.例子如下:

package main

import "fmt"

func main() {
    var ch = make(chan int, 20)
    for i := 0; i < 10; i++ {
        ch <- i
    }
    close(ch)
    //ch <- 11 //panic: runtime error: send on closed channel
    for i := range ch {
        fmt.Println(i) //输出0 1 2 3 4 5 6 7 8 9
    }
}

channel阻塞超时处理:
goroutine有时候会进入阻塞情况,那么如何避免由于channel阻塞导致整个程序阻塞的发生那?解决方案:通过select设置超时处理,具体程序如下:

package main

 import (
    "fmt"
    "time"
)

func main() {
    c := make(chan int)
    o := make(chan bool)
    go func() {
        for {
            select {
            case i := <-c:
                fmt.Println(i)
            case <-time.After(time.Duration(3) * time.Second):    //设置超时时间为3s,如果channel 3s钟没有响应,一直阻塞,则报告超时,进行超时处理.
                fmt.Println("timeout")
                o <- true
                break
            }
        }
    }()
    <-o
}


golang 并发总结:
并发两种方式:sync.WaitGroup,该方法最大优点是Wait()可以阻塞到队列中的所有任务都执行完才解除阻塞,但是它的缺点是不能够指定并发协程数量.
channel优点:能够利用带缓存的channel指定并发协程goroutine,比较灵活.但是它的缺点是如果使用不当容易造成死锁;并且他还需要自己判定并发goroutine是否执行完.

但是相对而言,channel更加灵活,使用更加方便,同时通过超时处理机制可以很好的避免channel造成的程序死锁,因此利用channel实现程序并发,更加方便,更加易用.

参考文献:

http://studygolang.com/articles/319

http://studygolang.com/articles/267