好库文摘 http://doc.okbase.net/ 多功能软件框架 http://doc.okbase.net/ck6666/archive/300977.html ck6666 2018/8/6 10:29:56 为什么要开源?

随着云计算、物联网和互联网+思维充斥我们的社会,企业信息化也已经进入一个全新的阶段。这对软件从业者和软件公司来说是前所未有的机遇也是前所未有的挑战。对软件公司和程序员来说时间就是生命和金钱,一套非开源的软件可以扼杀一个工程。

我们所开发这套框架是一套基于智能化可扩展组件式的软件系统项目,非常适合企业管理软件和互联网平台后台系统开发,框架中提供了完善的权限角色管理功能,快速开发功能工作流引擎开发功能等通用的功能模块,以及可扩展的系统机制,美观简洁UI界面风格

现在开始描述一下这个软件的具体情况,给有兴趣的朋友研究带来便利。

软件框架的作用

一、提高开发效率

     整体框架都已经搭建好了,开发者只需要实现业务功能。并且敏捷开发框架内已经集成了大量业务模板,大量的公共组件,开发人员只需要根据开发向导进行设置就可快速完成开发工作。比起传统的开发至少要节约90%的工作量,能够大大地提升开发效率。

二、提升软件质量

    规范的编码,专业的架构,稳定高效的底层。这是软件质量的先天优势。基于力软敏捷开发框架做开发的话,您的软件质量可以大幅提升,让您的软件赢在起跑线上!

三、降低成本

    本身在提高效率的同时就是在降低成本。现在软件工程师的工资一般都比较高,特别是架构师级别的动不动就数十万年薪,使得软件开发的成本变得非常之高。但是使用力软敏捷开发框架的条件下,初级程序员甚至只要思路清晰的人就可以进行功能开发。开发周期变短,对开发人员的要求变低,也使得开发成本大幅下降。

四、提高客户满意度

     力软敏捷开发框架为开发人员提供了美观简洁的UI美观大方、操作便捷,用户体验友好度必定大大提升。开发效率高、软件质量好,自然客户满意度也会大大提高。也会大大提升公司的接单率,给公司带来直接的效益!

五、提供一个稳定高效的技术支持团队

     维护期内由力软框架开发团队原班人马为您提供优质贴心的技术支持,不管是架构还是编码都能全方位地为您服务,让您不用担心开发过程中遇到的阻力,让您免去因员工流失而给软件项目带来的各种损失。有力软帮您的开发保驾护航,让您的开发更稳定更高效!

六、提供框架源代码,提供完整的授权

     框架提供全部源代码,毫不保留。您可以放心地拿这套框架去开发自己的产品,出售自己的产品时无需再次授权,毫无后顾之忧。

 

可二次开发的项目

一、业务管理软件

ERPMISCRMWMSMESTMS、物流快递管理等这类企业管理系统已经被几家大的软件公司产品化,但是每个行业都有不同的业务需求,每家企业都会有自己不同的业务需求。标准品无法做到面面俱到的所以我们很难采购到自己想要的产品。独立从头到尾开发一套系统需要大量的人力物力,到头来成本可能比采购软件成品还高,力软敏捷开发框架已经为开发都搭好框架预置了各类基础模块可以直接使用,另外系统根据各类系统的特点建立了多套开发模板,开发者可以按照开发向导快速开发出各种业务系统。

二、协同办公软件

    力软敏捷开发框架已经内置了工作流引擎、自定义表单引擎、即时通讯模块再配合框架完善的权限管理模块您可以轻松地定制自己的协同办公软件,实现OAHRMKM等系统的开发变得非常简单甚至不需要编写一行代码。

三、电商平台后台

    利用力软敏捷开发框架强大的后台管理功能及微信模块、短信平台模块开发电商平台后台也非常方便。

四、商业智能(BI)软件

力软敏捷开发框架集成了大量图表插件,并且提供了智能图表功能,开发者只需要按照向导操作就能生成图形报表。所以此框架也非常适合开发BI软件。

一些核心功能

1、拖拽式表单开发
这个设计是为了那些不懂程序的设计的,为了更好的深入客户的内心。

 
2.传统代码生成器与可视化设计结合生成代码


3、自定义报表


4APP开发


5、权限管理

6、基于Websocket的即时通讯组件

7、工作流引擎

8、新闻管理后台

9、文件资料

10、邮件中心

11、多数据库连接

12、常用示例

13、Excel的导入与导出

14、微信企业号

图片展示不了,抱歉,可以到官网上自己去操作,注册完账号就可以了。
www.learun.cn?fuid=e1

 

系统特色

、前端UI基于Jquery +Bootstrap,界面简洁大气,UI底层库提供了大量UI组件开发者轻松就能完成各种炫丽界面的设计。不像EXT、EasyUI那样外观千篇一律,另外也省去了UI的授权费用,毕竟EXT、EasyUI都需要收费的。

2、采用Ajax技术,页面无刷新,具有C/S系统一样优良的用户体验。此外采用Ajax交互使得前端不关心后台是何种技术架构(java.netphp,所以后台升级完全不会影响前台功能。

3、采用了Websocket技术,客户服务端之间可以建立长连接,使交互变得更加方便。

4、开发简单高效,不论您的开发水平在哪个层次都可以使用这套框架开发系统。开发成本低,速度快,效率高,开发出来的产品性能稳定。

5、一次性购买,无限分发销售,销售自己基于力软框架开发的系统时无需再次购买授权。

6、提供框架源代码,开发出来的产品可以申请知识产权。

7支持3个种类的数据库(OracleSQLserverMySQL),且有多个版本可选,支持多个数据库管理。

8、工作流组件简单易懂,功能强大,可配置性强,开发复杂的工作流也并没有什么问题。

9、提供了多套业务系统模板,您可以直接修改定制成自己个性化应用,不需要从头再开始设计功能。

10、提供了大量的通用插件,完成功能的开发就像搭积木一样,只需要把各种组件进行组合拼装,拼装好了,系统的开发也就完成了。

11、多维度、高细密度的权限管控,能满足各种变态的权限管控要求。

12、由框架开发团队的原版人马直接提供技术支持,为您顺利完成开发工作保驾护航。

13、不管您是零基础还是专业开发人员,都能轻松驾驭这套开发框架。

 

力软敏捷开发框架的优势

对比传统模式开发和基于闭源开发平台开发,您就可以相当明了地看出基于力软敏捷开发框架开发的优势了。程序员使用力软敏捷开发框架进行开发,只需要实现业务逻辑上的功能的编写,不需要把心思花费在架构上。大大减小了开发难度,缩短了开发时间。公司使用了力软敏捷开发框架,大大缩短了项目的开发周期,提高了效率。产品的稳定性提高了,质量提高了,接单的效率也就提高了。力软敏捷开发框架可以让程序员不加班!让公司减少成本,提高效益!

 

]]>
创作管理系统心得 http://doc.okbase.net/ck1188/archive/300976.html ck1188 2018/8/6 10:21:08 为什么要开源?

随着云计算、物联网和互联网+思维充斥我们的社会,企业信息化也已经进入一个全新的阶段。这对软件从业者和软件公司来说是前所未有的机遇也是前所未有的挑战。对软件公司和程序员来说时间就是生命和金钱,一套非开源的软件可以扼杀一个工程。

我们所开发这套框架是一套基于智能化可扩展组件式的软件系统项目,非常适合企业管理软件和互联网平台后台系统开发,框架中提供了完善的权限角色管理功能,快速开发功能工作流引擎开发功能等通用的功能模块,以及可扩展的系统机制,美观简洁UI界面风格

现在开始描述一下这个软件的具体情况,给有兴趣的朋友研究带来便利。

软件框架的作用

一、提高开发效率

     整体框架都已经搭建好了,开发者只需要实现业务功能。并且敏捷开发框架内已经集成了大量业务模板,大量的公共组件,开发人员只需要根据开发向导进行设置就可快速完成开发工作。比起传统的开发至少要节约90%的工作量,能够大大地提升开发效率。

二、提升软件质量

    规范的编码,专业的架构,稳定高效的底层。这是软件质量的先天优势。基于力软敏捷开发框架做开发的话,您的软件质量可以大幅提升,让您的软件赢在起跑线上!

三、降低成本

    本身在提高效率的同时就是在降低成本。现在软件工程师的工资一般都比较高,特别是架构师级别的动不动就数十万年薪,使得软件开发的成本变得非常之高。但是使用力软敏捷开发框架的条件下,初级程序员甚至只要思路清晰的人就可以进行功能开发。开发周期变短,对开发人员的要求变低,也使得开发成本大幅下降。

四、提高客户满意度

     力软敏捷开发框架为开发人员提供了美观简洁的UI美观大方、操作便捷,用户体验友好度必定大大提升。开发效率高、软件质量好,自然客户满意度也会大大提高。也会大大提升公司的接单率,给公司带来直接的效益!

五、提供一个稳定高效的技术支持团队

     维护期内由力软框架开发团队原班人马为您提供优质贴心的技术支持,不管是架构还是编码都能全方位地为您服务,让您不用担心开发过程中遇到的阻力,让您免去因员工流失而给软件项目带来的各种损失。有力软帮您的开发保驾护航,让您的开发更稳定更高效!

六、提供框架源代码,提供完整的授权

     框架提供全部源代码,毫不保留。您可以放心地拿这套框架去开发自己的产品,出售自己的产品时无需再次授权,毫无后顾之忧。

 

可二次开发的项目

一、业务管理软件

ERPMISCRMWMSMESTMS、物流快递管理等这类企业管理系统已经被几家大的软件公司产品化,但是每个行业都有不同的业务需求,每家企业都会有自己不同的业务需求。标准品无法做到面面俱到的所以我们很难采购到自己想要的产品。独立从头到尾开发一套系统需要大量的人力物力,到头来成本可能比采购软件成品还高,力软敏捷开发框架已经为开发都搭好框架预置了各类基础模块可以直接使用,另外系统根据各类系统的特点建立了多套开发模板,开发者可以按照开发向导快速开发出各种业务系统。

二、协同办公软件

    力软敏捷开发框架已经内置了工作流引擎、自定义表单引擎、即时通讯模块再配合框架完善的权限管理模块您可以轻松地定制自己的协同办公软件,实现OAHRMKM等系统的开发变得非常简单甚至不需要编写一行代码。

三、电商平台后台

    利用力软敏捷开发框架强大的后台管理功能及微信模块、短信平台模块开发电商平台后台也非常方便。

四、商业智能(BI)软件

力软敏捷开发框架集成了大量图表插件,并且提供了智能图表功能,开发者只需要按照向导操作就能生成图形报表。所以此框架也非常适合开发BI软件。

一些核心功能

1、拖拽式表单开发
这个设计是为了那些不懂程序的设计的,为了更好的深入客户的内心。

 
2.传统代码生成器与可视化设计结合生成代码


3、自定义报表


4APP开发


5、权限管理

6、基于Websocket的即时通讯组件

7、工作流引擎

8、新闻管理后台

9、文件资料

10、邮件中心

11、多数据库连接

12、常用示例

13、Excel的导入与导出

14、微信企业号

图片展示不了,抱歉了,可以到官网上自己去操作,注册完账号就可以了。
w w w.learun.cn?fuid=e1

 

系统特色

、前端UI基于Jquery +Bootstrap,界面简洁大气,UI底层库提供了大量UI组件开发者轻松就能完成各种炫丽界面的设计。不像EXT、EasyUI那样外观千篇一律,另外也省去了UI的授权费用,毕竟EXT、EasyUI都需要收费的。

2、采用Ajax技术,页面无刷新,具有C/S系统一样优良的用户体验。此外采用Ajax交互使得前端不关心后台是何种技术架构(java.netphp,所以后台升级完全不会影响前台功能。

3、采用了Websocket技术,客户服务端之间可以建立长连接,使交互变得更加方便。

4、开发简单高效,不论您的开发水平在哪个层次都可以使用这套框架开发系统。开发成本低,速度快,效率高,开发出来的产品性能稳定。

5、一次性购买,无限分发销售,销售自己基于力软框架开发的系统时无需再次购买授权。

6、提供框架源代码,开发出来的产品可以申请知识产权。

7支持3个种类的数据库(OracleSQLserverMySQL),且有多个版本可选,支持多个数据库管理。

8、工作流组件简单易懂,功能强大,可配置性强,开发复杂的工作流也并没有什么问题。

9、提供了多套业务系统模板,您可以直接修改定制成自己个性化应用,不需要从头再开始设计功能。

10、提供了大量的通用插件,完成功能的开发就像搭积木一样,只需要把各种组件进行组合拼装,拼装好了,系统的开发也就完成了。

11、多维度、高细密度的权限管控,能满足各种变态的权限管控要求。

12、由框架开发团队的原版人马直接提供技术支持,为您顺利完成开发工作保驾护航。

13、不管您是零基础还是专业开发人员,都能轻松驾驭这套开发框架。

 

力软敏捷开发框架的优势

对比传统模式开发和基于闭源开发平台开发,您就可以相当明了地看出基于力软敏捷开发框架开发的优势了。程序员使用力软敏捷开发框架进行开发,只需要实现业务逻辑上的功能的编写,不需要把心思花费在架构上。大大减小了开发难度,缩短了开发时间。公司使用了力软敏捷开发框架,大大缩短了项目的开发周期,提高了效率。产品的稳定性提高了,质量提高了,接单的效率也就提高了。力软敏捷开发框架可以让程序员不加班!让公司减少成本,提高效益!

 

]]>
第九章:特定于平台的API调用(四) http://doc.okbase.net/doclist/archive/300975.html doclist 2018/7/20 10:30:10

特定于平台的声音生成

现在为了本章的真正目的:给MonkeyTap发声。所有三个平台都支持API,允许程序动态生成和播放音频波形。这是MonkeyTapWithSound程序采用的方法。
商业音乐文件通常以诸如MP3之类的格式压缩。但是当一个程序算法算法生成波形时,未压缩的格式会更加方便。最基本的技术 - 所有三个平台都支持 - 称为脉冲编码调制或PCM。除了花哨的名字,它很简单,它是用于在音乐CD上存储声音的技术。
PCM波形由一系列恒定速率的样本描述,称为采样率。音乐CD使用标准速率为每秒44,100个样本。如果不需要高音质,计算机程序生成的音频文件通常使用一半(22,050)或四分之一(11,025)的采样率。可记录和再现的最高频率是采样率的一半。
每个样本都是固定大小,用于定义该时间点波形的幅度。音乐CD上的样本是带符号的16位值。当声音质量无关紧要时,8位样本很常见。某些环境支持浮点值。多个样本可以容纳立体声或任意数量的声道。对于移动设备上的简单音效,单声道声音通常很好。
MonkeyTapWithSound中的声音生成算法是针对16位单声道样本进行硬编码的,但采样率由常量指定,并且可以轻松更改。
现在您已经了解了DependencyService的工作原理,让我们检查添加到MonkeyTap的代码,将其转换为MonkeyTapWithSound,让我们从上到下看一下。为避免重现大量代码,新项目包含MonkeyTap项目中MonkeyTap.xaml和MonkeyTap.xaml.cs文件的链接。
在Visual Studio中,通过从项目菜单中选择“添加”>“现有项”,可以将项目添加为项目作为现有文件的链接。然后使用“添加现有项”对话框导航到该文件。从“添加”按钮的下拉列表中选择“添加为链接”。
在Xamarin Studio中,从项目的工具菜单中选择添加>添加文件。打开文件后,会弹出“添加文件到文件夹”警告框。选择“添加指向该文件的链接”。
但是,在Visual Studio中执行这些步骤后,还需要手动编辑Mon?keyTapWithSound.csproj文件,以将MonkeyTapPage.xaml文件更改为EmbeddedResource,将Generator更改为MSBuild:UpdateDesignTimeXaml。此外,还将一个DependentUpon标记添加到MonkeyTapPage.xaml.cs文件中以引用MonkeyTapPage.xaml文件。这会导致代码隐藏文件在文件列表中的XAML文件下缩进。
然后,MonkeyTapWithSoundPage类派生自MonkeyTapPage类。虽然MonkeyTapPage类是由XAML文件和代码隐藏文件定义的,但MonkeyTapWithSoundPage仅是代码。当以这种方式派生类时,必须将XAML文件中的事件的原始代码隐藏文件中的事件处理程序定义为受保护的,这是这种情况。
MonkeyTap类还将flashDuration常量定义为protected,并将两个方法定义为protected和virtual。 MonkeyTapWithSoundPage重写这两个方法来调用一个名为SoundPlayer.PlaySound的静态方法:

namespace MonkeyTapWithSound
{
    class MonkeyTapWithSoundPage : MonkeyTap.MonkeyTapPage
    {
        const int errorDuration = 500;
        // Diminished 7th in 1st inversion: C, Eb, F#, A
        double[] frequencies = { 523.25, 622.25, 739.99, 880 };
        protected override void BlinkBoxView(int index)
        {
            SoundPlayer.PlaySound(frequencies[index], flashDuration);
            base.BlinkBoxView(index);
        }
        protected override void EndGame()
        {
            SoundPlayer.PlaySound(65.4, errorDuration);
            base.EndGame();
        }
    }
}

SoundPlayer.PlaySound方法接受频率和持续时间(以毫秒为单位)。 每一件事 - 音量,声音的谐波组成以及声音是如何产生的 - 都是PlaySound方法的责任。 但是,此代码隐含地假设SoundPlayer.PlaySound立即返回,并且不等待声音完成播放。 幸运的是,所有这三个平台都支持以这种方式运行的声音生成API。
使用PlaySound静态方法的SoundPlayer类是MonkeyTapWithSound PCL项目的一部分。 此方法的职责是为声音定义PCM数据的数组。 此数组的大小取决于采样率和持续时间。 for循环计算定义所请求频率的三角波的样本:

namespace MonkeyTapWithSound
{
    class SoundPlayer
    {
        const int samplingRate = 22050;
        /* Hard-coded for monaural, 16-bit-per-sample PCM */
        public static void PlaySound( double frequency = 440, int duration = 250 )
        {
            short[] shortBuffer    = new short[samplingRate * duration / 1000];
            double    angleIncrement    = frequency / samplingRate;
            double    angle        = 0; /* normalized 0 to 1 */
            for ( int i = 0; i < shortBuffer.Length; i++ )
            {
                /* Define triangle wave */
                double sample;
                /* 0 to 1 */
                if ( angle < 0.25 )
                    sample = 4 * angle;
                /* 1 to -1 */
                else if ( angle < 0.75 )
                    sample = 4 * (0.5 - angle);
                /* -1 to 0 */
                else
                    sample = 4 * (angle - 1);
                shortBuffer[i]    = (short) (32767 * sample);
                angle        += angleIncrement;
                while ( angle > 1 )
                    angle -= 1;
            }
            byte[] byteBuffer = new byte[2 * shortBuffer.Length];
            Buffer.BlockCopy( shortBuffer, 0, byteBuffer, 0, byteBuffer.Length );
            DependencyService.Get().PlaySound( samplingRate, byteBuffer );
        }
    }
} 

虽然样本是16位整数,但是其中两个平台希望数据以字节数组的形式存在,因此使用Buffer.BlockCopy在末尾附近进行转换。 该方法的最后一行使用DependencyService将具有采样率的此字节数组传递给各个平台。
DependencyService.Get方法引用IPlatformSoundPlayer接口,该接口定义了PlaySound方法的签名:

namespace MonkeyTapWithSound
{
    public interface IPlatformSoundPlayer
    {
        void PlaySound(int samplingRate, byte[] pcmData);
    }
}

现在来了困难的部分:为三个平台编写这个PlaySound方法!
iOS版本使用AVAudioPlayer,它需要包含Wave?form音频文件格式(.wav)文件中使用的标头的数据。 这里的代码汇编了MemoryBuffer中的数据,然后将其转换为NSData对象:

using System;
using System.IO;
using System.Text;
using Xamarin.Forms;
using AVFoundation;
using Foundation;
[assembly: Dependency( typeof(MonkeyTapWithSound.iOS.PlatformSoundPlayer) )]
namespace MonkeyTapWithSound.iOS
{
    public class PlatformSoundPlayer : IPlatformSoundPlayer
    {
        const int    numChannels    = 1;
        const int    bitsPerSample    = 16;
        public void PlaySound( int samplingRate, byte[] pcmData )
        {
            int        numSamples    = pcmData.Length / (bitsPerSample / 8);
            MemoryStream    memoryStream    = new MemoryStream();
            BinaryWriter    writer        = new BinaryWriter( memoryStream, Encoding.ASCII );
            /* Construct WAVE header. */
            writer.Write( new char[] { 'R', 'I', 'F', 'F' } );
            writer.Write( 36 + sizeof(short) * numSamples );
            writer.Write( new char[] { 'W', 'A', 'V', 'E' } );
            writer.Write( new char[] { 'f', 'm', 't', ' ' } );              /* format chunk */
            writer.Write( 16 );                                             /* PCM chunk size */
            writer.Write( (short) 1 );                                      /* PCM format flag */
            writer.Write( (short) numChannels );
            writer.Write( samplingRate );
            writer.Write( samplingRate * numChannels * bitsPerSample / 8 ); /* byte rate */
            writer.Write( (short) (numChannels * bitsPerSample / 8) );      /* block align */
            writer.Write( (short) bitsPerSample );
            writer.Write( new char[] { 'd', 'a', 't', 'a' } );              /* data chunk */
            writer.Write( numSamples * numChannels * bitsPerSample / 8 );
            /* Write data as well. */
            writer.Write( pcmData, 0, pcmData.Length );
            memoryStream.Seek( 0, SeekOrigin.Begin );
            NSData        data        = NSData.FromStream( memoryStream );
            AVAudioPlayer    audioPlayer    = AVAudioPlayer.FromData( data );
            audioPlayer.Play();
        }
    }
}

请注意两个要点:PlatformSoundPlayer实现IPlatformSoundPlayer接口,并使用Dependency属性标记类。
Android版本使用AudioTrack类,结果更容易一些。 但是,AudioTrack对象不能重叠,所以有必要保存前一个对象并停止播放,然后开始下一个对象:

using System;
using Android.Media;
using Xamarin.Forms;
[assembly: Dependency( typeof(MonkeyTapWithSound.Droid.PlatformSoundPlayer) )]
namespace MonkeyTapWithSound.Droid
{
    public class PlatformSoundPlayer : IPlatformSoundPlayer
    {
        AudioTrack previousAudioTrack;
        public void PlaySound( int samplingRate, byte[] pcmData )
        {
            if ( previousAudioTrack != null )
            {
                previousAudioTrack.Stop();
                previousAudioTrack.Release();
            }
            AudioTrack audioTrack = new AudioTrack( Stream.Music,
                                samplingRate,
                                ChannelOut.Mono,
                                Android.Media.Encoding.Pcm16bit,
                                pcmData.Length * sizeof(short),
                                AudioTrackMode.Static );
            audioTrack.Write( pcmData, 0, pcmData.Length );
            audioTrack.Play();
            previousAudioTrack = audioTrack;
        }
    }
}

三个Windows和Windows Phone平台可以使用MediaStreamSource。 为了避免大量重复代码,MonkeyTapWithSound解决方案包含一个名为WinRuntimeShared的额外SAP项目,该项目仅由三个平台都可以使用的类组成:

using System;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Media.Core;
using Windows.Media.MediaProperties;
using Windows.Storage.Streams;
using Windows.UI.Xaml.Controls;
namespace MonkeyTapWithSound.WinRuntimeShared
{
    public class SharedSoundPlayer
    {
        MediaElement    mediaElement = new MediaElement();
        TimeSpan    duration;
        public void PlaySound( int samplingRate, byte[] pcmData )
        {
            AudioEncodingProperties audioProps =
                AudioEncodingProperties.CreatePcm( (uint) samplingRate, 1, 16 );
            AudioStreamDescriptor    audioDesc    = new AudioStreamDescriptor( audioProps );
            MediaStreamSource    mss        = new MediaStreamSource( audioDesc );
            bool            samplePlayed    = false;
            mss.SampleRequested += (sender, args) =>
            {
                if ( samplePlayed )
                    return;
                IBuffer            ibuffer = pcmData.AsBuffer();
                MediaStreamSample    sample    =
                    MediaStreamSample.CreateFromBuffer( ibuffer, TimeSpan.Zero );
                sample.Duration        = TimeSpan.FromSeconds( pcmData.Length / 2.0 / samplingRate );
                args.Request.Sample    = sample;
                samplePlayed        = true;
            };
            mediaElement.SetMediaStreamSource( mss );
        }
    }
}

此SAP项目由三个Windows和Windows Phone项目引用,每个项目包含相同的(命名空间除外)PlatformSoundPlayer类:

using System;
using Xamarin.Forms;
[assembly: Dependency( typeof(MonkeyTapWithSound.UWP.PlatformSoundPlayer) )]
namespace MonkeyTapWithSound.UWP
{
    public class PlatformSoundPlayer : IPlatformSoundPlayer
    {
        WinRuntimeShared.SharedSoundPlayer sharedSoundPlayer;
        public void PlaySound( int samplingRate, byte[] pcmData )
        {
            if ( sharedSoundPlayer == null )
            {
                sharedSoundPlayer = new WinRuntimeShared.SharedSoundPlayer();
            }
            sharedSoundPlayer.PlaySound( samplingRate, pcmData );
        }
    }
}

使用DependencyService来执行特定于平台的杂务是非常强大的,但是当涉及到用户界面元素时,这种方法不足。 如果您需要扩展装饰Xamarin.Forms应用程序页面的视图库,那么这项工作涉及创建特定于平台的渲染器,这是本书最后一章中讨论的过程。

]]>
云数据库架构演进与实践 http://doc.okbase.net/31534344/archive/300974.html OliverFinn 2018/7/20 10:29:46

如今,大型企业如金融企业和银行等,在下一代的微服务架构转型要求下,需要基础软件和数据平台能够实现原生的云化,以满足微服务架构的需求。

微服务,也就是一种面向服务的,有特定边界的松散耦合的架构。

主要特点包括,每一个微服务是一个独立的自治系统,可以不依赖外部组件独立运行;对应用只暴露接口,用户可以灵活的调整过每个微服务的使用;业务粒度足够小。

 

在企业架构“云化”的过程中,数据库的云化是最为重要也是难度较大的一个部分。数据库云平台(dbPaaS)是一类支持弹性扩张、多租户、自我管理、并能够运行在云服务提供商的基础设施(IaaS)之上的数据库管理系统(DBMS)或存储管理系统。

 

根据Gartner报告预测,数据库云平台市场份额将会在下一个五年中翻倍,而70%的用户将开始使用dbPaaS数据库云平台。因此,为了满足各类应用程序对数据库云平台的需求,同时为了减少私有云部署中对大量不同类型数据存储产品的运维复杂性,数据库的架构演进将是未来十年数据库转型的主要方向之一。

 

 

 

云数据库的技术需求

在业务和应用进行“云化”的过程中,云数据库因为在整体架构中的重要地位,在云化改造中的重要性不言而喻。云数据库的核心需求有一下几点,主要有:

· 弹性扩张能力:数据库容量需要根据业务弹性扩展,满足不同业务的容量需求;

· 弹性部署与随需应变能力:除了数据库的存储,其他数据库功能也需要根据应用的需求,进行弹性的部署调整;

· 数据可靠性与服务持续能力:数据的可靠安全,全时在线是所有业务的必须要求;

· 计算存储分离:将计算和存储资源灵活配置,既可以选择多种计算方式也可以同时对应多种存储方式,满足更多业务需求;

· 多模式存储能力:结构化、非结构化、半结构化和图等多类型数据的存储;

· 自我管理能力:提供零停机维护、持续集成、以及滚动升级能力,提升开发人员效率;

· 自我监控以及问题修复能力:故障监控和问题修复,降低运维成本;

· 是否满足特定应用场景:针对特定场景的可插拔组件或工具;

· 监管与安全:满足监管的要求,保证数据的安全。

 

云数据库需要满足这些技术要求,除了在功能上的具体提升,在整体架构上更需要进行升级和“进化”。

 

 

云数据库架构方向

云数据库架构是其能否承载应用架构“云化”的关键点,随着技术和业务的发展,云数据库的架构出现了几个主要的发展方向:

· 在dbPaaS平台中,计算-存储层分离将会成为主流技术方向。通过将协议解析、计算等模块与底层存储解耦,数据库云平台将存储层进行分片以实现存储的弹性水平扩张,同时通过计算层的无状态设计允许计算层通过增加节点数量线性提升计算能力,已达到整个数据库云平台的弹性水平扩张。

· 多模架构成为主流趋势,Multi-model的架构在一个数据库平台就可以支持多种存储方式,大大减少运维和开发的成本。传统数据库中例如IBM、Oracle等早已经提供关系型、OO、甚至XML等存储引擎。而新一代数据库则更提供NewSQL、JSON、图、对象存储等多种类型数据存储引擎。

· 云数据库平台将会提供多种混合模式的数据服务 – 关系型与非关系型。该模式使用户能够在同一平台中结合不同数据存储类型的特点,为新一代IT应用系统提供混合数据存储解决方案。

· 更符合微服务业务架构的要求,微服务要求各个服务模块之间尽量松耦合和可独立扩展。因此对于数据库,也同样会针对不同的业务,进行不同侧重的配置,无论是传统的“读写分离”或者现在流行的HTAP都是围绕这个要求展开的。

 

针对这几个主要的发展方向,我们就将详细来探讨云数据库的几个重要技术特点。

 

1)存储-SQL 分离

针对云数据库的需求和架构方向,一种新的数据库架构也在渐渐成为主流,也就是数据库的 “存储-SQL分离”架构。

 

存储-SQL分离架构,即指数据库的存储引擎和SQL引擎两部分互相松耦合独立工作的架构。通常这一架构,分为存储、SQL和元数据 三个部分。

 

· 存储层:即数据库的存储引擎,存储引擎负责处理数据的存储管理。同时包含路由及事务控制,保障数据的ACID特性。此外,存储层还应还具备索引、查询条件过滤、排序等一系列功能。

· SQL层:SQL层主要负责处理SQL请求,上层直接面对应用程序,将应用程序的访问请求分发给存储层,并且接受存储层返回的数据结果。

· 元数据区:元数据区负责存储整个数据库的所有元数据信息。

 

典型的云数据库架构示意

 

对于这一架构,其实MySQL数据库当前的架构是有一些类似的。

MySQL数据库的SQL、存储分离的架构,在架构较为灵活,而其开源的生态也支持将不同的产品、引擎和工具进行充分的对接。在存储引擎的架构上,插件式的存储引擎架构将查询处理和其它的系统任务以及数据的存储提取相分离。这种架构可以根据业务的需求和实际需要选择合适的存储引擎。

 

MySQL数据库整体技术模块架构

 

如上图所示,MySQL 的存储引擎可以挂载多种不同的产品,每个引擎都能提供不同的技术特性。其中包括InnoDB、MyISAM等架构。

 

存储与SQL分离的架构,目前在数据库业界十分流行,AWS的Aurora数据库在SQL访问上也采用了类似的架构。SequoiaDB 3.0 目前在MySQL兼容上,主要也是采取“SQL-存储分离“的架构。

 

 

SequoiaDB 3.0 MySQL 兼容逻辑架构

 

SequoiaDB 3.0使用了MySQL数据库原生的SQL解析器,天然支持MySQL协议并可以做到100%语法兼容。在该架构中,MySQL协议解析层作为SQL解析和分发的角色,直接面对应用程序,每一个MySQL服务的接入节点都是一个独立支持读写操作的MySQL进程。而数据存储和管理层,则完全由巨杉数据库的分布式数据库引擎实现。简单来说,SequoiaDB 3.0作为MySQL的InnoDB替换引擎,在天然支持MySQL的全部语法和功能的同时,提供了数据库存储层弹性扩张的能力。

 

 

 

2)多模Multi-Model

企业使用云数据库对接的应用越来越多,需求多种多样,传统的做法是在dbPaaS里面提供十几个不同的数据库产品分别应对各种需求,这样的方法在系统增加后,整体维护性和数据一致性管理成本很高,会影响到整个系统的使用。

 

云数据库的“多模”示意图

 

为了实现业务数据的统一管理和数据融合,新型数据库需要具备多模式(Multi-Model)数据管理和存储的能力。数据库多模Multi-Model是指同一个数据库支持多个存储引擎,可以同时满足应用程序对于结构化、半结构化、非结构化数据的统一管理需求。

 

通常来说,结构化数据特指表单类型的数据存储结构,典型应用包括银行核心交易等传统业务;而半结构化数据则在用户画像、物联网设备日志采集、应用点击流分析等场景中得到大规模使用;非结构化数据则对应着海量的的图片、视频、和文档处理等业务,在金融科技的发展下增长迅速。

 

多模式数据管理能力,使得金融级数据库能够进行跨部门、跨业务的数据统一存储与管理,实现多业务数据融合,支撑多样化的金融服务。

 

在架构上,刚刚提到的多模Multi-model也是针对云数据库需求的,则使得数据库使用一套数据管理体系可以支撑多种数据类型,因此支持多种业务模式,大大降低使用和运维的成本。

 

3)灾备和多活

对于应用程序来说,开发人员并不希望在设计应用的过程当中花费大量的精力来考虑底层数据高可用、灾备与多活时应用的切换逻辑。一般来说,一个成熟的dbPaaS层应当尽可能将底层的数据多副本同步、灾难切换、高可用接管等一系列操作进行封装,对于应用程序做到完全透明。

 

在传统的应用程序开发中,开发者使用中间件容器对数据源进行配置,底层使用F5或其他虚拟IP地址对多个数据源进行封装。但是,在云化的演变过程中,底层的数据库从单一节点向分布式节点过渡,对于上层的应用程序一方面希望尽可能减少应用程序设计时对分库分表的依赖,另一方面更希望在数据节点切换,甚至数据中心灾难接管的过程当中做到应用透明无感知。

 

SequoiaDB 3.0则引入了异地多活的架构,应用程序可以从任意接入节点以读写的方式访问本地数据库。在数据读写的过程当中,巨杉数据库能够从底层有效地进行数据一致性控制,对多个地区本地写入的数据进行远程复制,确保多个站点所读写的数据完全一致。

 

另外,灾难发生时巨杉数据库提供对应用程序透明的数据切换与接管机制,动态调整底层数据分布拓扑逻辑,能够动态有效地排除故障数据中心内的节点,做到其他站点无感知地继续提供数据服务。

 

多活相比于传统的高可用来说,不仅在性能和安全性上实现了更大的提升,而这一架构也能在多活数据中心中充分的应用软硬件设备,减少冗余。

 

云数据库架构优势

在技术驱动的需求下,云数据库架构具备了几项主要的业务价值:

· 无需分库分表:此前,一种数据库分布式改造的方向是关系型数据库往分布式架构改造,MySQL分库分表就是其中一种方案。如今,存储-SQL分离的架构,在数据存储层已经实现原生分步实施,就避免了复杂冗长的“分库分表”方案。

· 灵活支撑业务需求:存储和SQL层都可以实现服务、存储的弹性调整,灵活地支撑业务的需求。

· 多存储引擎兼容:由于SQL和存储层的分离,在保持SQL接口不变的情况下,底层存储引擎可以支撑多个不同引擎,实现多种数据引擎的同时兼容。

· 完全兼容已有应用:由于SQL层更多使用已有的标准SQL解析器,因此对于原有应用在SQL上可以实现完全的兼容,没有任何应用改造的投入。

· 数据安全可用:分布式的存储和松耦合的架构,数据拥有安全的多副本,松耦合则大大增强了整个系统的容错性。相比传统单点架构,可以很好的实现数据双活甚至多活的架构,满足“两地三中心”“三地五中心”的合规监管安全要求。

 

 

云数据库应用场景

在新架构驱动下,云数据库目前在多个场景下已经开始实现落地应用。

 

传统交易服务

在传统中心化交易型业务中,高性能、高吞吐量的数据存储与处理能力,ACID以及安全都是非常重要的特性。例如,在一个典型的银行业务中,为了满足高峰时期的在线交易量,交易型数据库需要在亿级记录条数的数据库中每秒处理上千比交易。同时,为了满足生产系统的健壮性与可靠性,传统交易服务对于底层数据存储的安全性、高可用性、两地三中心部署能力都有着非常明确的要求。

 

因此云数据库既需要将传统交易型业务逐渐转移至云平台,同时也需要在满足安全性和合规监管方面,为用户提供更好的支持。

 

历史数据服务

近年来,随着IT技术与大数据的不断发展,越来越多的企业将数据作为自身宝贵的资产进行长期保留。这使得一些传统应用程序的历史数据包袱越来越重,最终数据库不堪重负导致应用整体性能低下。另一方面,随着大数据需求的不断增加,曾经已经归档的数据需要重新在线以满足在线化、实时化使用、查询和分析等等要求,这就要求将原有庞大的离线数据进行“在线化”。这些需求使得历史数据管理成为必须。

对于历史数据服务来说,由于对外提供应用程序的直接访问,其健壮性、可靠性、可配置一致性策略、性能与并发能力都是极为值得关注的。同时,相对传统交易服务来说,强一致和ACID反倒并不是最关注的点。鉴于一些企业直接将部分报表和自助查询运行在历史服务平台上,HTAP的能力也是值得关注的特性。

 

云数据库在扩展性和性能上通过分布式的架构满足了这些需求,将历史数据很好的管理起来。

 

实时在线服务

当前大部分企业的生产业务系统与后台的数据加工、分析与查询系统都是通过T+1的方式进行数据ETL。而最近随着流处理技术的兴起,越来越多的企业开始基于流处理技术构建T+0的数据总线,以实现不同业务流程之间实时数据对接。譬如说,用户资产视图就可以利用流处理技术,在提供用户全资产视图查询的优秀用户体验的同时,大幅度减轻其对后台生产系统造成的查询压力。

对于实时在线服务来说,数据库的层面最为关注性能、吞吐量、可靠性、与可用性。而对于强一致、ACID、与HTAP来说并不构成其最重要的特性。

在线业务的数据多样化和性能都需要云架构的数据库提供更灵活高效的支持。

 

 

影像存储服务

很多行业在业务运营中会产生大量纸质凭证,在信息化处理和监管要求下,这些纸质的凭证都需要扫描成影像文件并长期保存。随着互联网技术以及集中作业中心等理念的深入推广,大量行业普遍需要建设统一的影像管理平台。

对于典型的影像平台来说,其存储的数据总体量极大,使用传统存储的单位成本很高,需要进行生命周期管理时对运维又非常复杂。因此,对于逐年递增的海量影像数据来说,大部分企业都存在查询难、管理难、扩容难的几大痛点。

同时,由于影像存储服务已经成为很多流程的一部分,其稳定性、可靠性与健壮性与核心交易系统处于同一级别。因此,影像存储服务最关注的层面在于可靠性、一致性、可扩展性、吞吐量、以及非结构化存储的多模特性。而其对于交易的ACID、HTAP等特性并不重点关注。

 

 

小结

云数据库是未来数据库发展的一个重要方向,云数据库架构随着云化要求也需要进行相应的迭代,未来在云数据库架构的演进还会随着需求的变化而持续发展。

其中对于多模数据引擎、计算存储分离等将是云数据库技术演进的重点方向。

巨杉也会持续关注架构的迭代演进,同时也在技术和架构上针对云架构进行更多的创新。

]]>
中小团队快速构建SQL自动审核系统 http://doc.okbase.net/37Y37/archive/300973.html 运维咖啡吧 2018/7/20 10:21:07

SQL审核与执行,作为DBA日常工作中相当重要的一环,一直以来我们都是通过人工的方式来处理,效率低且质量没办法保证。为了规范操作,提高效率,我们决定引入目前市面上非常流行的SQL自动审核工具Inception。

花了一周看了inception官方文档并搭建了测试环境简单测试,又花了3天写了webui(内部起名叫overmind,以下也简称overmind),目前正在进行各种测试以及手册的编写,还未正式跟我们的流程结合起来投入使用,后续正式使用后再更新使用经验。

Inception

Inception是一个开源的Mysql自动化工具,具有SQL审核、执行、回滚等实用的功能,由国内大神基于mysql源码开发,可以很明确的,详细的,准确的审核Mysql的SQL语句,工作模式与Mysql完全相同,可以直接使用mysql客户端来连接。但遗憾的是2年前已停止更新,不过兼容大部分的mysql版本,仍然是开源SQL审核工具的翘楚。

Inception的架构如下:

inception架构图

Overmind

审核流程

我们的审核流程现在主要分两部分:

1.Dev和Qa等线下测试环境:为了简化流程,开发测试环境,可以由开发直接通过overmind系统进行审核或执行

2.Prod生产环境:生产环境以安全为主,在Dev和Qa环境执行成功后详情页面会有个“申请上线”按钮,点击申请上线自动发送邮件给DBA,待DBA审核通过后方可上线

系统介绍

  • 基本配置页面:可以直接在页面上配置inception的信息,方便迁移
    基本配置页

  • 数据库信息录入:相当于一个简单数据库的cmdb系统,可以管理数据库信息
    数据库信息录入

  • 审核任务提交:支持审核和执行两种模式,审核模式只给审核结果,执行模式在执行之前会先审核
    审核任务提交

  • 审核结果展示:详细展示SQL审核的所有信息,包括提交用户、执行操作、审核结果等等
    审核结果展示

  • 任务列表页面:可以很方便只查看自己提交的任务,或者查看固定项目的,固定环境的任务
    任务列表页

关于开源

开源在我看来是一件很隆重的事情,之前刚学python的时候开源了自己写的一个简单后台sadmin(github或gitee可以搜索到),因为一些原因停止更新了。目前没有时间和精力来做长久的支持和维护,所以暂时不打算把源代码放到github上。另外同类型的已经有一个非常棒的开源产品Yearning了,可以github搜索安装学习。

写在最后

  1. 先说感谢!感谢大神开发的Inception,绝对是DBA界的福音
  2. 之所以能就花了3天时间写了overmind,主要还是借助了之前已写好的其他项目框架,在此基础上做修改,还是很快的
  3. 工具部署与页面编写都是比较简单的,团队内部都可以搞定,要把工具推出去,融入到现在的流程中是一件比较困难的事情,给团队内定的规则就是a.完善的文档,用户在审核前就知晓哪些sql通不过,在审核失败时知晓如何修改能成功,b.对用户的感知应降到最低,不要因为用了overmind而造成额外的麻烦,要让用户用的爽
  4. 希望作者或其他大神能持续更新Inception,继续造福广大DBA

欢迎关注公众号

]]>
论文笔记:多标签学习综述(A review on multi-label learning algorithms) http://doc.okbase.net/liaohuiqiang/archive/300972.html PilgrimHui 2018/7/20 10:21:00

2014 TKDE(IEEE Transactions on Knowledge and Data Engineering)
张敏灵,周志华

简单介绍
传统监督学习主要是单标签学习,而现实生活中目标样本往往比较复杂,具有多个语义,含有多个标签。本综述主要介绍了多标签学习的一些相关内容,包括相关定义,评价指标,8个多标签学习算法,相关的其它任务。

论文大纲

  1. 相关定义:学习任务,三种策略
  2. 评价指标:基于样本的评价指标,基于标签的评价指标
  3. 学习算法:介绍了8个有代表性的算法,4个基于问题转化的算法和4个基于算法改进的算法
  4. 相关任务:多实例学习,有序分类,多任务学习,数据流学习

相关定义

  1. 学习任务
    \(X = \mathbb{R}^{d}\)表示d维的输入空间,\(Y=\{y_1, y_2, ..., y_q\}\)表示带有q个可能标签的标签空间。
    训练集$D = {(x^i, y^i)| 1 \leq i \leq m} $,m表示训练集的大小,上标表示样本序数,有时候会省略。
    \(x^i \in X\),是一个d维的向量。\(y^i \subseteq Y\),是\(Y\)的一个标签子集。
    任务就是要学习一个多标签分类器\(h(\cdot )\),预测\(h(x) \subseteq Y\)作为x的正确标签集。
    常见的做法是学习一个衡量x和y相关性的函数\(f(x, y_j)\),希望\(f(x, y_{j1}) > f(x, y_{j2})\),其中\(y_{j1} \in y, y_{j2} \notin y\)
    \(h(x)\)可以由\(f(x)\)衍生得到,\(h(x) = \{y_j | f(x,y_j) > t(x), y_j \in Y\}\)
    \(t(x)\)扮演阈值函数的角色,把标签空间对分成相关的标签集和不相关的标签集。
    阈值函数可以由训练集产生,可以设为常数。当\(f(x, y_j)\)返回的是一个概率值时,阈值函数可设为常数0.5。

  2. 三种策略
    多标签学习的主要难点在于输出空间的爆炸增长,比如20个标签,输出空间就有\(2^{20}\),为了应对指数复杂度的标签空间,需要挖掘标签之间的相关性。比方说,一个图像被标注的标签有热带雨林和足球,那么它具有巴西标签的可能性就很高。一个文档被标注为娱乐标签,它就不太可能和政治相关。有效的挖掘标签之间的相关性,是多标签学习成功的关键。根据对相关性挖掘的强弱,可以把多标签算法分为三类。
  • 一阶策略:忽略和其它标签的相关性,比如把多标签分解成多个独立的二分类问题(简单高效)。
  • 二阶策略:考虑标签之间的成对关联,比如为相关标签和不相关标签排序。
  • 高阶策略:考虑多个标签之间的关联,比如对每个标签考虑所有其它标签的影响(效果最优)。

评价指标
可分为两类

  • 基于样本的评价指标(先对单个样本评估表现,然后对多个样本取平均)
  • 基于标签的评价指标(先考虑单个标签在所有样本上的表现,然后对多个标签取平均)

每类又可分为用于分类任务和用于排序任务的指标,具体指标如下图所示

下面对图中的每个指标进行介绍。

基于样本的评价指标

  1. Subset Accuracy(衡量正确率,预测的样本集和真实的样本集完全一样才算正确。)
    \[ \frac{1}{p}\sum_{i=1}^{p} 1\{h(x^i)=y^i\}\]
    其中p表示测试集的样本大小,\(1\{\pi\}\)表示\(\pi\)为真时返回1,否则返回0。

  2. Hamming Loss(衡量的是错分的标签比例,正确标签没有被预测以及错误标签被预测的标签占比)
    \[ \frac{1}{p}\sum_{i=1}^{p}\frac{1}{q}\left | h(x^i)\Delta y^i \right | \]
    其中\(\Delta\)表示两个集合的对称差,返回只在其中一个集合出现的那些值。

  3. Accuracy, Precision, Recall, F值(单标签学习中准确率,精准率,召回率,F值的天然拓展)
    \[ Accuracy(h) = \frac{1}{p}\sum_{i=1}^{p}\frac{\left | h(x^i)\cap y^i \right |} {\left | h(x^i)\cup y^i \right |} \]
    \[ Precision(h) = \frac{1}{p}\sum_{i=1}^{p}\frac{\left | h(x^i)\cap y^i \right |} {\left | h(x^i) \right |} \]
    \[ Recall(h) = \frac{1}{p}\sum_{i=1}^{p}\frac{\left | h(x^i)\cap y^i \right |} {\left | y^i \right |} \]
    \[ F^{\beta}(h) = \frac{(1+\beta^2) \cdot Precision(h) \cdot Recall(h)}{\beta^2 \cdot Precision(h) \cdot Recall(h)}\]

  4. One-error(度量的是:“预测到的最相关的标签” 不在 “真实标签”中的样本占比。值越小,表现越好)
    \[ one-error(f) = \frac{1}{p}\sum_{i=1}^{p} 1\{ [arg \; \mathop {max}\limits_{y_j \in Y} \; f(x^i, y_j)] \notin y^i\} \]

  5. Coverage(度量的是:“排序好的标签列表”平均需要移动多少步,才能覆盖真实的相关标签集)
    \[ coverage(f) = \frac{1}{p}\sum_{i=1}^{p} \mathop {max}\limits_{y_j \in y^i} \; rank_f(x^i, y_j) - 1\]
    其中$ rank_f(x^i, y_j) $ 表示用\(f(\cdot, \cdot)\)\(Y\)中的所有标签(注意是对\(Y\)中所有标签)进行降序排序,给个排名,最后返回的是\(y_j\)标签在这个排序列表中的一个排名,排名越大,相关性越小。而 \(\mathop {max}\limits_{y_j \in y^i}\)表示取到,真实标签\(y^i\)中的标签在上面这个排名中最大的,那个排名。
    如果真实标签\(y^i\)被完全预测正确的话,取到的值是$\left | y^i \right | \(,\)y^i\(中的排名就是从1到\)\left | y^i \right | \(。 如果\)y^i\(中有一个标签\)y_j\(没有被预测正确,那么取的值就是那个标签\)y_j\(在\)Y\(中的排名,因为预测正确的那些都是排名最小(相关性最大)的那些标签,这个\)y_j\(肯定是大于\)\left | y^i \right | $的。

  6. Ranking Loss(度量的是:反序标签对的占比,也就是不相关标签比相关标签的相关性还要大的情况)
    \[ rloss(f) = \frac{1}{p}\sum_{i=1}^{p} \frac{1}{ \left | y^i \right | \left | \overline{y^i}\right | } \left | \{ (y_{j1}, y_{j2}) \; | \; f(x^i, y_{j1}) \leq f(x^i, y_{j2}) \; , \; (y_{j1}, y_{j2}) \in (y^i \times \overline{y^i}) \}\right | \]
    其中\(\overline{y^i}\)\(y^i\)\(Y\)上的补集。\(y_{j1}\)从相关的标签集\(y^i\)中取,\(y_{j2}\)从不相关的标签集$ \overline{y^i}$中取,两两组合形成标签对。

  7. Average Precision(度量的是:比特定标签更相关的那些标签的排名的占比)
    \[ avgprec(f) = \frac{1}{p}\sum_{i=1}^{p} \frac{1}{ \left | y^i \right | } \sum_{y_{j1} \in y^i}\frac{\left | \{ y_{j2} \; | \; rank_f(x^i, y_{j2}) \leq rank_f(x^i, y_{j1}) \; , \; y_{j2} \in y^i\} \right |}{rank_f(x^i, y_{j1})} \]

基于标签的评价指标

  1. Macro-averaging
    \[B_{macro}(h)= \frac{1}{q}\sum_{j=1}^{q}B(TP_j,\;FP_j,\; TN_j,\; FN_j) \]

  2. Micro-averaging
    \[B_{micro}(h)= B(\sum_{j=1}^{q}TP_j,\; \sum_{j=1}^{q}FP_j,\; \sum_{j=1}^{q}TN_j,\; \sum_{j=1}^{q}FN_j)\]
    其中\(TP_j,\;FP_j,\; TN_j,\; FN_j\)为单个标签下传统二分类的四个数量特征,真正例,假正例,真负例,假负例。
    $B \in { Accuracy, Precision, Recall, F^\beta } $ 表示对四个数量特征进行相关运算得到常规的二分类指标。
    macro是先对单个标签下的数量特征计算得到常规指标,再对多个标签取平均。
    micro是先对多个标签下的数量特征取平均,再根据数量特征计算得到常规指标。

  3. AUC-macro(度量的是:“排序正确”的数据对的占比,macro是先对单个标签计算,再平均)
    (这里的“排序正确”指的是根据\(f(\cdot,\cdot)\)函数,对于相关标签的打分会大于不相关标签的打分
    \[ AUC_{macro} = \frac{1}{q} \sum_{j=1}^{q} \frac{\left | \{ ({x}',{x}'')\;|\; f({x}',y_j) \geq f({x}'',y_j) \; , \; ({x}',{x}'') \in Z_j \times \overline{Z_j} \}\right |}{\left | Z_j \right | \left | \overline{Z_j} \right |} \]
    其中\(Z_j = \{x^i \;|\; y_j \in y^i, 1 \leq i \leq p \}\)表示的是含有\(y_j\)标签的样本数量
    其中\(\overline{Z_j} = \{x^i \;|\; y_j \notin y^i, 1 \leq i \leq p \}\)表示的是不含\(y_j\)标签的样本数量。

  4. AUC-micro(度量的是:“排序正确”的数据对的占比,micro是直接把多个标签考虑在内来计算占比)
    \[AUC_{micro} = \frac{\left | \{ ({x}', {x}'', {y}', {y}'')\;|\; f({x}',{y}') \geq f({x}'',{y}'') \; , \; ({x}',{y}') \in S^+ \; , \; ({x}'',{y}'') \in S^- \}\right |}{\left | S^+ \right | \left | S^- \right |} \]
    其中\(S^+= \{(x^i, y_j) \;|\; y_j \in y^i, 1 \leq i \leq p \}\)表示的是相关的样本标签对
    其中\(S^- = \{(x^i, y_j) \;|\; y_j \notin y^i, 1 \leq i \leq p \}\)表示的是不相关的样本标签对

学习算法
可分为两类(具体算法如下图所示)

  • 问题转换的方法:把多标签问题转为其它学习场景,比如转为二分类,标签排序,多分类
  • 算法改编的方法:通过改编流行的学习算法去直接处理多标签数据,比如改编懒学习,决策树,核技巧。

下面对图中的每个算法进行介绍。
Binary Relevance
把多个标签分离开来,对于q个标签,建立q个数据集和q个二分类器来进行预测。
这是最简单最直接的方法,是其它先进的多标签算法的基石。
没有考虑标签之间的关联性,是一个一阶策略(first-order)

Classifier Chains
首先按特定的顺序(这个顺序是自己决定的)对q个标签排个序,得到\(y_{\tau(1)}\succ y_{\tau(2)} \succ ...\succ y_{\tau(q)}\)。对于第j个标签\(y_{\tau(j)}\)构建一个二分类的数据集

\[D_{\tau(j)}=\{ ([x^i, pre^i_{\tau(j)}], 1\{ y_{\tau(j)} \in y^i \}) \; | \; 1 \leq i \leq m\} \\ where \ pre^i_{\tau(j)}=(1\{ y_{\tau(1)} \in y^i \},...,1\{ y_{\tau(j-1)} \in y^i \})^T\]

第j个标签构建的二分类数据集中,\(x^i\)会concat上前j-1个标签值。
以这样chain式的方法构建q个数据集,训练q个分类器。
在预测阶段,由于第j个分类器需要用到前j-1个分类器预测出的标签集,所以需要顺序调用这q个分类器来预测。

  1. 显然算法的好坏会受到顺序\(\tau\)的影响,可以使用集成的方式,使用多个随机序列,对每个随机序列使用一部分的数据集进行训练。
  2. 虽然该算法把问题分解成多个二分类,但由于它以随机的方式考虑了多个标签之间的关系,所以它是一个高阶策略(high-order)。
  3. 该算法的一个缺点是丢失了平行计算的机会,因为它需要链式调用来进行预测

Calibrated Label Ranking
算法的基本思想是把多标签学习问题转为标签排序问题,该算法通过“成对比较”来实现标签间的排序。
对q个标签,可以构建q(q-1)/2个标签对,所以可以构建q(q-1)/2个数据集。
\[ D_{jk} = \{ (x_i, \psi (y^i, y_j, y_k)) \; | \; \phi (y^i, y_j) \neq \phi (y^i, y_k), 1 \leq i \leq m \} \\ where \ \psi (y^i, y_j, y_k)) = \left\{\begin{matrix} +1, & if \ \phi (y^i, y_j) = +1 \ and \ \phi (y^i, y_k) = -1\\ -1, & if \ \phi (y^i, y_j) = -1 \ and \ \phi (y^i, y_k) = +1 \end{matrix}\right. \\ \phi (y^i, y_j) = \left\{\begin{matrix} +1 & if \ y_j \in y^i\\ -1 & else \end{matrix}\right.\]

  1. 只有带有不同相关性的两个标签\(y_j\)\(y_k\)的样本才会被包含在数据集\(D_{jk}\)中,用该数据集训练一个分类器,当分类器返回大于0时,样本属于标签\(y_j\),否则属于标签\(y_k\)
  2. 可以看到,每个样本\(x^i\)会被包含在\(\left | y^i \right | \left | \overline{y^i} \right |\)个分类器中。
  3. 在预测阶段,根据分类器,每个样本和某个标签会产生一系列的投票,根据投票行为来做出最终预测。
  4. 前面构造二分类器的方法使用one-vs-rest的方式,本算法使用one-vs-one,缓和类间不均衡的问题。
  5. 缺点在于复杂性高,构建的分类器个数为q(q-1)/2,表现为二次增长。
  6. 考虑两个标签之间的关联,是二阶策略(second-order)

Random k-Labelsets
算法的基本思想是把多标签学习问题转为多分类问题。把\(2^q\)个可能的标签集,映射成\(2^q\)个自然数。
映射函数记为\(\sigma _Y\),则原数据集变为\(D^+_Y = \{ (x^i, \sigma_Y(y^i)) \ | \ 1 \leq i \leq m \}\)
所对应的新类别记为 $ \Gamma(D^+_Y) = { \sigma_Y(y^i)  |  1 \leq i \leq m}\(,显然\) \left | \Gamma(D^+_Y) \right | \leq min(m, 2^{|Y|})$。
这样来训练一个多分类器,最后根据输出的自然数映射回标签集的算法称为LP(Label Powerest)算法,它有两个主要的局限性

  1. LP预测的标签集是训练集中已经出现的,它没法泛化到未见过的标签集
  2. 类别太大,低效

为了克服LP的局限性,Random k-Labelsets使用的LP分类器只训练Y中的一个长度为k的子集,然后集成大量的LP分类器来预测。
\(Y^k\)表示\(Y\)的所有的长度为k的子集,\(Y^k(l)\)表示随机取的一个长度为k的子集,这样就可以进行收缩样本空间,得到如下样本集和标签集。
\[D^+_{Y^k(l)}= \{ (x^i, \sigma_{Y^k(l)}(y^i \cap Y^k(l) )) \ | \ 1 \leq i \leq m \}\]
\[\Gamma(D^+_{Y^k(l)})= \{ \sigma_{Y^k(l)}(y^i \cap Y^k(l) ) \ | \ 1 \leq i \leq m \}\]

更进一步,我们随机取n个这样的子集:\(Y^k(l_r), 1 \leq r \leq n\)来构造n个分类器做集成。
最后预测的时候需要计算两个指标,一个为标签j能达到的最大投票数,一个为实际投票数。
\[\tau(x, y_j) = \sum_{r=1}^{n} 1\{ y_j \in Y^k(l_r)\}\]
\[\mu (x, y_j) = \sum_{r=1}^{n} 1\{ y_j \in \sigma_{Y^k(l)}^{-1}(g^+_{Y^k(l)}(x) )\} \]
其中$ \sigma_{Y^k(l)}^{-1}(\cdot)\(表示从自然数映射回标签集的函数,\)g^+(\cdot)$表示分类器学习到的函数。最后预测的时以0.5为阈值进行预测,得到标签集。
\[y = \{ y_j \ | \ \mu (x, y_j) \ / \ \tau(x, y_j) > 0.5\ , \ 1 \leq j \leq q\}\]
因为是随机长度为k的子集,考虑了多个标签之间的相关性,所以是高阶策略(high-order)。

Multi-Label k-Nearest Neighbor(ML-KNN)
\(N(x)\)表示x的\(k\)个邻居,则\(C_j = \sum_{(x,y) \in N(x)} 1\{y_j \in y\}\)表示样本x的邻居中带有标签\(y_j\)的邻居个数。 用\(H_j\)表示样本x含有标签\(y_j\),根据后验概率最大化的规则,有
\[y = \{y_j \ | \ P(H_j \ | \ C_j)\ / \ P(\urcorner H_j \ | \ C_j) > 1 \ , \ 1 \leq j \leq q \}\]
根据贝叶斯规则,有
\[ \frac{P(H_j \ | \ C_j)} {P(\urcorner H_j\ | \ C_j)} = \frac {P(H) \cdot P(C_j \ | \ H_j)} {P(\urcorner H) \cdot P(C_j \ | \ H_j)}\]
先验概率\(P(H_j), P(\urcorner H_j)\)可以通过训练集计算得到,表示样本带有或不带有标签\(y_q\)的概率
\[P(H_j) = \frac { s + \sum_{i=1}^{m} 1\{ y_j \in y^i \} } {s \times 2 + m} \\ P(\urcorner H_j) = 1 - P(H_j) \ \ (1 \leq j \leq q)\]
其中s是平滑因子,s为1时则使用的是拉普拉斯平滑。
条件概率的计算需要用到两个值
\[\kappa_j[r] = \sum_{i=1}^{m} 1\{ y_j \in y^i \} \cdot 1\{ \delta_j(x^i) = r \} \ \ \ \ (0 \leq r \leq k) \\ \tilde{\kappa}_j[r] = \sum_{i=1}^{m} 1\{ y_j \notin y^i \} \cdot 1\{ \delta_j(x^i) = r \} \ \ \ \ (0 \leq r \leq k) \\ where \ \ \delta_j(x^i) = \sum_{(x^*,y^*) \in N(x^i)} 1 \{y_j \in y^*\}\]
\(\kappa_j[r]\)表示“含有标签\(y_j\)而且r个邻居也含有标签\(y_j\)的”样本的个数。
\(\tilde{\kappa}_j[r]\)表示“不含有标签\(y_j\)但是r个邻居含有\(y_j\)的”样本的个数。
根据这两个值,可以计算相应的条件概率
\[P(C_j \ | \ H_j) = \frac{s+\kappa_j[C_j]} {s \times (k+1) + \sum_{r=0}^{k} \kappa_j[r]} \ \ (1 \leq j \leq q, 0 \leq C_j \leq k) \\ P(C_j \ | \ \urcorner H_j) = \frac{s+\tilde{\kappa}_j[C_j]} {s \times (k+1) + \sum_{r=0}^{k} \tilde{\kappa}_j[r]} \ \ (1 \leq j \leq q, 0 \leq C_j \leq k) \]
这两个条件概率表示的是,样本带有或不带有标签\(y_j\)的条件下,它有\(C_j\)个邻居带有标签\(y_j\)的概率。

  1. 由上述的条件概率,先验概率则可以根据贝叶斯规则和后验概率最大化,计算出样本的标签集
  2. 需要注意的是该方法不是KNN和独立二分类的简单结合,因为算法中还使用了贝叶斯来推理邻居信息
  3. 没有考虑标签之间的相关性,是一阶策略(first-order)

Multi-Label Decision Tree(ML-DT)
使用决策树的思想来处理多标签数据,数据集T中,使用第l个特征,划分值为\(\vartheta\),计算出如下信息增益:
\[IG(T, l, \vartheta ) = MLEnt(T) - \sum_{\rho \in \{-, +\} } \frac{|T^{\rho }|} {\left | T \right |} \cdot MLEnt(T^{\rho}) \\ where \ \ T^- = \{ (x^i, y^i) \ | \ x_{il} \leq v, 1 \leq i \leq n\} \\ where \ \ T^+ = \{ (x^i, y^i) \ | \ x_{il} \gt v, 1 \leq i \leq n\} \]
递归地构建一颗决策树,每次选取特征和划分值,使得上式的信息增益最大。
其中式子中的熵的公式可以按如下计算(为了方便计算,假定标签之间独立)。
\[MLEnt(T) = \sum_{j=1}^{q} -p_j log_2p_j - (1-p_j)log_2(1-p_j) \\ where \ \ p_j= \frac {\sum_{i=1}^{n} 1\{ y_j \in y^i \}} {n}\]

  1. 新样本到来时,向下遍历决策树的结点,找到叶子结点,若\(p_j\)大于0.5则表示含有标签\(y_j\)
  2. 该算法不是决策树和独立二分类的简单结合(如果是的话,应该构建q棵决策树)
  3. 没有考虑标签的相关性,是一阶策略(first-order)

Ranking Support Vector Machine(Rank-SVM)
使用最大间隔的思想来处理多标签数据。
Rank-SVM考虑系统对相关标签和不相关标签的排序能力。
考虑最小化\(x^i\)到每一个“相关-不相关”标签对的超平面的距离,来得到间隔。
\[\min_{(x^i, y^i) \in D} \min_{(y_j, y_k) \in y^i \times \overline{y^i}} \frac{ \langle w_j-w_k,x^i \rangle +b_j-b_k}{\left \| w_j - w_k\right \|}\]
像SVM一样对w和b进行缩放变换后可以对式子进行改写,然后最大化间隔,再调换分子分母进行改写,得到:
\[ \begin{matrix} \min_{w} & \max_{1 \leq j < k \leq q} {\left \| w_j - w_k\right \|^2}\\ subject\ to: & \; \langle w_j - w_k, x^i \rangle + b_j - b_k \geq 1 \\ & (1 \leq i \leq m, \ \ (y_i,y_k) \in y^i \times \overline{y^i}) \end{matrix}\]
为了简化,用sum操作来近似max操作
\[ \begin{matrix} \min_{w} & \sum_{j=1}^q {\left \| w_j \right \|^2}\\ subject\ to: & \; \langle w_j - w_k, x^i \rangle + b_j - b_k \geq 1 \\ & (1 \leq i \leq m, \ \ (y_i,y_k) \in y^i \times \overline{y^i}) \end{matrix} \]
跟SVM一样,为了软间隔最大化,引入松弛变量,得到下式:
\[ \begin{matrix} \min_{w, \Xi } & \sum_{j=1}^q {\left \| w_j \right \|^2} + C \sum_{i=1}^m \frac {1}{\left | y^i \right | \left | \overline{y^i} \right | } \sum_{(y_i,y_k) \in y^i \times \overline{y^i})} \xi _{ijk} \\ subject\ to: & \; \langle w_j - w_k, x^i \rangle + b_j - b_k \geq 1 - \xi _{ijk}\\ & \xi _{ijk} > 0 \ (1 \leq i \leq m, \ \ (y_i,y_k) \in y^i \times \overline{y^i}) \end{matrix} \\\]
其中\(\Xi = \{ \xi_{ijk} \ | \ 1 \leq i \leq m, \ (y_i,y_k) \in y^i \times \overline{y^i} \}\)

  1. 跟SVM一样,最终的式子是一个二次规划问题,通常调用现有的包来解。
  2. 对于非线性问题则使用核技巧来解决。
  3. 由于定义了”相关-不相关“标签对的超平面,这是个二阶策略(second-order)

Collective Multi-Label Classifier(CML)
该算法的核心思想最大熵原则。用\((x,y),\)表示任意的一个多标签样本,其中\(y = (y_1, y_2, ..., y_q) \in \{-1, +1\}^q\)
算法的任务等价于学习一个联合概率分布\(p(x,y)\),用\(H_p(x,y)\)表示给定概率分布\(p\)\((x,y)\)的信息熵。
最大熵原则认为熵最大的模型是最好的模型。
\[ \begin{matrix} &\max_{p} H_p(x,y) \\ &subject \ to: E_p[f_k(x,y)] = F_k \ (k \in K) \end{matrix} \]
其中\(f_k(x,y)\)是一个特征函数,描述\(x\)\(y\)之间的一个事实\(k\),满足这个事实时返回1,否则返回0。
约束做的是希望这个分布上,特征函数的期望能够等于一个我们希望的值\(F_k\),这个值通常通过训练集来估计。
解这个优化问题,会得到
\[p(y|x) = \frac{1}{Z_{\Lambda}(x) } exp(\sum_{k \in K} \lambda_k \cdot f_k(x,y)) \]
其中\(\Lambda = \{ \lambda_k | k \in K \}\)表示一系列的权重。$Z_{\Lambda} = \sum_y exp(\sum_{k \in K} \lambda_k \cdot f_k(x,y)) \(作为规范化因子。 假设有一个高斯先验\)\lambda_k \sim N(0, \varepsilon^2)\(,就可以通过最大化以下这个log后验概率来求得参数\)\Lambda$。
\[ \begin{matrix} l(\Lambda | D) & = log P(D|\Lambda) + log P(\Lambda) \\ & = log \prod_{(x,y) \in D} p(y|x) + log P(\Lambda) \\ & = log(\prod_{(x,y) \in D} p(y|x)) - \sum_{k \in K} \frac {\lambda^2}{2 \varepsilon^2} \\ \end{matrix}\]

  1. 这是个凸函数,可以调用现成的无约束优化方法比如BFGS直接求解。求得参数就可以得到要学习的概率分布\(p(y|x)\)
  2. 对于一系列约束K,分为两个部分
  3. \(K_1 = \{ (l,j) | 1 \leq l \leq d, 1 \leq j \leq q\}\),有\(d \cdot q\)个约束,特征函数为
    \[f_k(x,y) = x_l \cdot 1 \{ y_j == 1 \} , \ \ k = (l,j) \in K_1\]
  4. $K_2 = { (j_1, j_2, b_1, b_2) | 1 \leq j_1 < j_2 \leq q,   b_1, b_2 \in { -1, +1 } } $,有\(4 \cdot \binom{q}{2}\)个约束,特征函数为
    \[ f_k(x,y) = 1 \{ y_{j1} = b_1 \} \cdot 1 \{ y_{j2} = b_2 \}, \ \ k = (j_1, j_2, b_1, b_2) \in K_2\]
  5. 由于K约束中考虑了标签对之间的关联,该算法是个二阶策略(second-order)。

相关任务

  1. 多实例学习(Multi-instance learning):每个样本由多个实例和一个标签组成,多个实例中至少一个为正,认为该样本为正。和多标签学习的输出空间模糊相反,多实例学习是输入空间模糊。
  2. 有序分类(Ordinal classification):对于每个标签,不再是简单地判断是还是否,而是改成一系列的等级排序,把\(y_j = \{-1,+1\}\)替换成\(y_j = \{m_1, m_2, ..., m_k\}, \ where \ m_1 < m_2 < ... < m_k\)
  3. 多任务学习(Multi-task learning):同时训练多个任务,相关任务之间的训练信息会帮助其它任务。比如目标定位既要识别有没有目标(分类问题)又要定位出目标的位置(回归问题)。
  4. 数据流学习(Data streams classification):真实世界的目标是在线生成和实时产生的,如何处理这些数据就是数据流学习要做的事。一个关键的挑战就是“概念漂移”(目标变量的统计特性随着时间的推移以不可预见的方式变化),一般处理方式有:当一大批新数据到来时更新分类器;维持一个检测器来警惕概念漂移;假定过去数据的影响会随着时间而衰减。

总结

  1. 论文主要介绍了多标签学习的一些概念定义,策略,评价指标,以及8个有代表性的算法,其中对多种评价指标和多个算法都做了清晰的分类和详细的阐述。
  2. 尽管挖掘标签关联性的想法被应用到许多算法中,但是仍然没有一个正式的机制。有研究表示多标签之间的关联可能是非对称的(我对你的影响和你对我的影响是不同的),局部的(不同样本之间的标签相关性不同,很少关联性是所有样本都满足的)。
  3. 但是不管怎么说,充分理解和挖掘标签之间的相关性,是多标签学习的法宝。尤其是巨大输出空间场景下。
]]>
基于 Redis 的分布式锁 http://doc.okbase.net/crossoverJie/archive/300971.html crossoverJie 2018/7/20 8:51:41

前言

分布式锁在分布式应用中应用广泛,想要搞懂一个新事物首先得了解它的由来,这样才能更加的理解甚至可以举一反三。

首先谈到分布式锁自然也就联想到分布式应用。

在我们将应用拆分为分布式应用之前的单机系统中,对一些并发场景读取公共资源时如扣库存,卖车票之类的需求可以简单的使用同步或者是加锁就可以实现。

但是应用分布式了之后系统由以前的单进程多线程的程序变为了多进程多线程,这时使用以上的解决方案明显就不够了。

因此业界常用的解决方案通常是借助于一个第三方组件并利用它自身的排他性来达到多进程的互斥。如:

  • 基于 DB 的唯一索引。
  • 基于 ZK 的临时有序节点。
  • 基于 Redis 的 NX EX 参数。

这里主要基于 Redis 进行讨论。

实现

既然是选用了 Redis,那么它就得具有排他性才行。同时它最好也有锁的一些基本特性:

  • 高性能(加、解锁时高性能)
  • 可以使用阻塞锁与非阻塞锁。
  • 不能出现死锁。
  • 可用性(不能出现节点 down 掉后加锁失败)。

这里利用 Redis set key 时的一个 NX 参数可以保证在这个 key 不存在的情况下写入成功。并且再加上 EX 参数可以让该 key 在超时之后自动删除。

所以利用以上两个特性可以保证在同一时刻只会有一个进程获得锁,并且不会出现死锁(最坏的情况就是超时自动删除 key)。

加锁

实现代码如下:


    private static final String SET_IF_NOT_EXIST = "NX";
    private static final String SET_WITH_EXPIRE_TIME = "PX";
    
    public  boolean tryLock(String key, String request) {
        String result = this.jedis.set(LOCK_PREFIX + key, request, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, 10 * TIME);

        if (LOCK_MSG.equals(result)){
            return true ;
        }else {
            return false ;
        }
    }

注意这里使用的 jedis 的

String set(String key, String value, String nxxx, String expx, long time);

api。

该命令可以保证 NX EX 的原子性。

一定不要把两个命令(NX EX)分开执行,如果在 NX 之后程序出现问题就有可能产生死锁。

阻塞锁

同时也可以实现一个阻塞锁:

    //一直阻塞
    public void lock(String key, String request) throws InterruptedException {

        for (;;){
            String result = this.jedis.set(LOCK_PREFIX + key, request, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, 10 * TIME);
            if (LOCK_MSG.equals(result)){
                break ;
            }
                
              //防止一直消耗 CPU  
            Thread.sleep(DEFAULT_SLEEP_TIME) ;
        }

    }
    
     //自定义阻塞时间
     public boolean lock(String key, String request,int blockTime) throws InterruptedException {

        while (blockTime >= 0){

            String result = this.jedis.set(LOCK_PREFIX + key, request, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, 10 * TIME);
            if (LOCK_MSG.equals(result)){
                return true ;
            }
            blockTime -= DEFAULT_SLEEP_TIME ;

            Thread.sleep(DEFAULT_SLEEP_TIME) ;
        }
        return false ;
    }

解锁

解锁也很简单,其实就是把这个 key 删掉就万事大吉了,比如使用 del key 命令。

但现实往往没有那么 easy。

如果进程 A 获取了锁设置了超时时间,但是由于执行周期较长导致到了超时时间之后锁就自动释放了。这时进程 B 获取了该锁执行很快就释放锁。这样就会出现进程 B 将进程 A 的锁释放了。

所以最好的方式是在每次解锁时都需要判断锁是否是自己的。

这时就需要结合加锁机制一起实现了。

加锁时需要传递一个参数,将该参数作为这个 key 的 value,这样每次解锁时判断 value 是否相等即可。

所以解锁代码就不能是简单的 del了。

    public  boolean unlock(String key,String request){
        //lua script
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";

        Object result = null ;
        if (jedis instanceof Jedis){
            result = ((Jedis)this.jedis).eval(script, Collections.singletonList(LOCK_PREFIX + key), Collections.singletonList(request));
        }else if (jedis instanceof JedisCluster){
            result = ((JedisCluster)this.jedis).eval(script, Collections.singletonList(LOCK_PREFIX + key), Collections.singletonList(request));
        }else {
            //throw new RuntimeException("instance is error") ;
            return false ;
        }

        if (UNLOCK_MSG.equals(result)){
            return true ;
        }else {
            return false ;
        }
    }

这里使用了一个 lua 脚本来判断 value 是否相等,相等才执行 del 命令。

使用 lua 也可以保证这里两个操作的原子性。

因此上文提到的四个基本特性也能满足了:

  • 使用 Redis 可以保证性能。
  • 阻塞锁与非阻塞锁见上文。
  • 利用超时机制解决了死锁。
  • Redis 支持集群部署提高了可用性。

使用

我自己有撸了一个完整的实现,并且已经用于了生产,有兴趣的朋友可以开箱使用:

maven 依赖:

<dependency>
    <groupId>top.crossoverjie.opensource</groupId>
    <artifactId>distributed-redis-lock</artifactId>
    <version>1.0.0</version>
</dependency>

配置 bean :

@Configuration
public class RedisLockConfig {

    @Bean
    public RedisLock build(){
        RedisLock redisLock = new RedisLock() ;
        HostAndPort hostAndPort = new HostAndPort("127.0.0.1",7000) ;
        JedisCluster jedisCluster = new JedisCluster(hostAndPort) ;
        // Jedis 或 JedisCluster 都可以
        redisLock.setJedisCluster(jedisCluster) ;
        return redisLock ;
    }

}

使用:

    @Autowired
    private RedisLock redisLock ;

    public void use() {
        String key = "key";
        String request = UUID.randomUUID().toString();
        try {
            boolean locktest = redisLock.tryLock(key, request);
            if (!locktest) {
                System.out.println("locked error");
                return;
            }


            //do something

        } finally {
            redisLock.unlock(key,request) ;
        }

    }

使用很简单。这里主要是想利用 Spring 来帮我们管理 RedisLock 这个单例的 bean,所以在释放锁的时候需要手动(因为整个上下文只有一个 RedisLock 实例)的传入 key 以及 request(api 看起来不是特别优雅)。

也可以在每次使用锁的时候 new 一个 RedisLock 传入 key 以及 request,这样倒是在解锁时很方便。但是需要自行管理 RedisLock 的实例。各有优劣吧。

项目源码在:

https://github.com/crossoverJie/distributed-lock-redis

欢迎讨论。

单测

在做这个项目的时候让我不得不想提一下单测

因为这个应用是强依赖于第三方组件的(Redis),但是在单测中我们需要排除掉这种依赖。比如其他伙伴 fork 了该项目想在本地跑一遍单测,结果运行不起来:

  1. 有可能是 Redis 的 ip、端口和单测里的不一致。
  2. Redis 自身可能也有问题。
  3. 也有可能是该同学的环境中并没有 Redis。

所以最好是要把这些外部不稳定的因素排除掉,单测只测我们写好的代码。

于是就可以引入单测利器 Mock 了。

它的想法很简答,就是要把你所依赖的外部资源统统屏蔽掉。如:数据库、外部接口、外部文件等等。

使用方式也挺简单,可以参考该项目的单测:

    @Test
    public void tryLock() throws Exception {
        String key = "test";
        String request = UUID.randomUUID().toString();
        Mockito.when(jedisCluster.set(Mockito.anyString(), Mockito.anyString(), Mockito.anyString(),
                Mockito.anyString(), Mockito.anyLong())).thenReturn("OK");

        boolean locktest = redisLock.tryLock(key, request);
        System.out.println("locktest=" + locktest);

        Assert.assertTrue(locktest);

        //check
        Mockito.verify(jedisCluster).set(Mockito.anyString(), Mockito.anyString(), Mockito.anyString(),
                Mockito.anyString(), Mockito.anyLong());
    }

这里只是简单演示下,可以的话下次仔细分析分析。

它的原理其实也挺简单,debug 的话可以很直接的看出来:

这里我们所依赖的 JedisCluster 其实是一个 cglib 代理对象。所以也不难想到它是如何工作的。

比如这里我们需要用到 JedisCluster 的 set 函数并需要它的返回值。

Mock 就将该对象代理了,并在实际执行 set 方法后给你返回了一个你自定义的值。

这样我们就可以随心所欲的测试了,完全把外部依赖所屏蔽了

总结

至此一个基于 Redis 的分布式锁完成,但是依然有些问题。

  • 如在 key 超时之后业务并没有执行完毕但却自动释放锁了,这样就会导致并发问题。
  • 就算 Redis 是集群部署的,如果每个节点都只是 master 没有 slave,那么 master 宕机时该节点上的所有 key 在那一时刻都相当于是释放锁了,这样也会出现并发问题。就算是有 slave 节点,但如果在数据同步到 salve 之前 master 宕机也是会出现上面的问题。

感兴趣的朋友还可以参考 Redisson 的实现。

号外

最近在总结一些 Java 相关的知识点,感兴趣的朋友可以一起维护。

地址: https://github.com/crossoverJie/Java-Interview

]]>
如何在Netweaver SE16里直接查看某数据库行记录 http://doc.okbase.net/JerryWang_SAP/archive/300970.html JerryWang_SAP 2018/7/20 8:09:15

有的数据库表字段类型为RAWSTRING,

 

包含的是XML的二进制内容,无法直接在SE16里显示。

 

如果确实想看其内容,怎么办?在下面SE16页面的命令提示栏输入命令/h, 回车进入调试模式。然后双击某一行:

 

断点会触发如下。

 

稍稍调试几步,能看到一个SELECT SINGLE语句,这个操作为了取待显示在明细页面上的单条记录。双击SQL取回的结果。

 

在调试器里显示的还是二进制代码:

 

从下拉菜单里选择XML Browser,XML的内容就出来了。

 

要获取更多Jerry的原创技术文章,请关注公众号"汪子熙"或者扫描下面二维码:

 
 
]]>
SAP CRM Survey调查问卷的存储模型 http://doc.okbase.net/JerryWang_SAP/archive/300969.html JerryWang_SAP 2018/7/20 8:09:09

数据库表CRM_SVY_DB_SVS,通过如下的函数CRM_SVY_DB_SVS_CREATE插入:

 

可以通过指定的创建者和创建时间很容易查找到特定的Survey:

 

调查问卷的答案明细以XML的格式存储:

 

2. 数据库表CRM_SVY_DB_SV

每个调查问卷的答案的明文存储,查看起来比CRM_SVY_DB_SVS方便。

 
 

通过函数CRM_SVY_VALUES_SET_INTERN插入。

 
 
 

要获取更多Jerry的原创技术文章,请关注公众号"汪子熙"或者扫描下面二维码:

 
 
]]>
使用gradle构建java项目3 http://doc.okbase.net/hw1287789687/archive/300968.html hw1287789687 2018/7/20 8:08:17

 

gradle 是什么

gradle 是一个让构建自动化的工具,类似于maven,ant的功能.
使用gradle可以给java项目编译,单元测试,打包,或者生成可执行的jar包等

gradle的依赖环境

gradle依赖java环境,所以使用gradle前需要安装jdk 或jre

gradle 构建项目的流程

gradle的构建依赖于task, task可以指定与其他task之间的依赖关系 比如,有两个task,walk 和bike,如果指定walk依赖bike,那么 执行walk前会先执行bike.

task的来源有两种:

  1. 1.插件提供,gradle有很多现成的插件;



 

 

  1. 2.自定义:在build.gradle 文件中声明task


 

task 能实现哪些功能

下面是一些常用的基本功能

复制

 

task walk(description:'walk') {
    doLast {
        copy {
            into 'demo'
            exclude '**/.svn/**'
            from('README.md')
        }
    }
}

  

 

删除

 

task walk(description:'walk') {
    doLast {
        println 'walk...'
      
        project.delete {
            delete 'README.md'
            followSymlinks = true
        }
        
    }
}

参考:https://docs.gradle.org/current/dsl/org.gradle.api.Project.html#N1512A 

 

 

 

 

如何执行任务

方式一:使用全局命令

gradle <任务名>



 

方式二:使用项目中的脚本

./gradlew <任务名>



 

引入本地jar包

 

compile project.fileTree(dir:'/Users/whuanghkl/code/mygit/myproject/target',include:['io0007-0.0.1.jar'])

 

 

gradle的插件有哪些

参考 https://docs.gradle.org/current/userguide/userguide.html



 

如何查询依赖的版本

gradle中依赖的仓库有多种:


参考:https://docs.gradle.org/current/dsl/org.gradle.api.artifacts.dsl.RepositoryHandler.html

我们可以选择 maven仓库: 

 

repositories {
    mavenCentral()
}

 

 

那么查询依赖就和maven一样了.

生成可执行的jar包

我的项目是spring boot,所以需要引入插件'org.springframework.boot'
id 'org.springframework.boot' version '2.0.3.RELEASE'
需要在build.gradle 文件中 指定可执行jar的main class :

 

jar {
    manifest {
        attributes 'Main-Class': 'com.kunlunsoft.Application'
    }
}

 

 

执行任务bootJar 就可以生成可执行的jar包





  

gradle 与maven相比有哪些优势

  1. 因为基于 groovy,功能更强大,可以很方便的自定义任务;
  2. 添加依赖更简洁方便,maven 需要三行,gradle只需要一行;
  3. 任务的执行流程更灵活,不像maven的生命周期那么固定.

我项目中完整的build.gradle 文件如下:

 

plugins {
    id 'java'
    id 'base'
//    id 'application'
    id 'org.springframework.boot' version '2.0.3.RELEASE'
}


//mainClassName = "com.kunlunsoft.Application"
group 'com.kunlunsoft'
version '1.0.0-SNAPSHOT'

sourceCompatibility = 1.8
targetCompatibility = 1.8

repositories {
    mavenCentral()
}

jar {
    manifest {
        attributes 'Main-Class': 'com.kunlunsoft.Application'
    }
}
task walk(description:'walk') {
    doLast {
        println 'walk...'
    }
}



dependencies {
    testCompile group: 'junit', name: 'junit', version: '4.12'
    testCompile("org.springframework.boot:spring-boot-starter-test")
    //数据源
    compile 'org.springframework.boot:spring-boot-starter:1.5.14.RELEASE'
    compile 'org.springframework.boot:spring-boot-starter-web:1.5.14.RELEASE'
    compile 'org.springframework.boot:spring-boot-starter-data-redis:1.5.14.RELEASE'
    compile 'mysql:mysql-connector-java:5.1.38'

    compile project.fileTree(dir:'/Users/whuanghkl/code/myproject/target',include:['io0007-0.0.1-SNAPSHOT.jar'])
    compile 'com.google.guava:guava:23.0-rc1'
    compile 'org.apache.commons:commons-email:1.5'
    compile 'org.codehaus.jackson:jackson-mapper-lgpl:1.9.12'
    //redis
//    compile 'org.springframework.data:spring-data-redis:1.8.13.RELEASE'
    compile 'redis.clients:jedis:2.9.0'
    compile 'org.springframework.statemachine:spring-statemachine-core:1.2.0.RELEASE'
    compile 'com.alibaba:fastjson:1.2.47'

//配置mybatis
    compile "org.mybatis.spring.boot:mybatis-spring-boot-starter:1.1.1"

    compile 'org.springframework.boot:spring-boot-gradle-plugin:1.5.14.RELEASE'
//    compile 'org.springframework:springloaded:1.5.14.RELEASE'
}

 

IDEA建议安装如下插件



 

 

 

参考:https://my.oschina.net/huangweiindex/blog/1844872

https://my.oschina.net/huangweiindex/blog/1842459

 

  • 大小: 48.7 KB
]]>