Lua的集成开发环境ZeroBrane Studio

目前在使用Lua进行脚本的开发,可是官方并没有提供很好的集成开发环境。
体验了很多,发现ZeroBrane Studio这个开源软件还是非常好用的,并且已经能正常支持Linux,Windows,MacOS这三个主流平台。

建议去官方网站下载最新的版本,但是鉴于国内网络访问不是非常稳定,可以从本站下载一份目前最新的版本。

下面的版本根据自身操作系统来选择其中一个进行下载

https://www.mobibrw.com/wp-content/uploads/2018/09/ZeroBraneStudioEduPack-1.70-linux.sh_.zip

https://www.mobibrw.com/wp-content/uploads/2018/09/ZeroBraneStudioEduPack-1.70-macos.dmg_.zip

https://www.mobibrw.com/wp-content/uploads/2018/09/ZeroBraneStudioEduPack-1.70-win32.exe_.zip

树莓派下的编译(目前编译出的暂时无法使用):

$ git clone https://github.com/pkulchenko/ZeroBraneStudio.git

#如果代码下载存在问题,可以本站下载一份拷贝
# wget https://www.mobibrw.com/wp-content/uploads/2018/09/ZeroBraneStudio.tar.xz
# tar xvf ZeroBraneStudio.tar.xz

$ cd ZeroBraneStudio

$ cd build

$ bash build-linux-prep-deb.sh

# gthread
$ sudo apt-get install libglib2.0-dev

# gtk+
$ sudo apt-get install libgtk2.0-dev

$ sudo apt-get install libgtk-3-dev

#opengl
$ sudo apt-get install freeglut3-dev

# ssl for luasec
$ sudo apt-get install libssl-dev

$ sudo ln -s /usr/lib/arm-linux-gnueabihf/libssl.so /usr/lib/libssl.so

# lua
$ sudo apt-get install lua5.1 liblua5.1-dev

#webview可选
#sudo apt-get install libwebkitgtk-dev

# luasec最新版本,早期版本编译不通过
$ sed -i "s/^LUASEC_BASENAME="luasec-0.6"/LUASEC_BASENAME="luasec-0.7"/g" build-linux.sh

#此处wxWidgets的克隆比较慢,因此可以本站下载一份拷贝,手工修改脚本的下载
# wget https://www.mobibrw.com/wp-content/uploads/2018/09/wxWidgets.tar.xz
# sed -i "s/^[ t]*git clone "$WXWIDGETS_URL".*/  wget https://www.mobibrw.com/wp-content/uploads/2018/09/wxWidgets.tar.xzn  rm -rf wxWidgetsn  tar xvf wxWidgets.tar.xz/g" build-linux.sh
# wget 
# sed -i "s/^[ t]*git clone "$WXLUA_URL" "$WXLUA_BASENAME".*/ wget https://www.mobibrw.com/wp-content/uploads/2018/09/wxlua.tar.xzn rm -rf wxluan tar xvf wxlua.tar.xz/g" build-linux.sh
# sed -i "s/^LEXLPEG_URL="https://foicica.com/scintillua/download/LEXLPEG_URL="https://www.mobibrw.com/wp-content/uploads/2018/09/g" build-linux.sh

# for debug "bash build-linux.sh debug all"
$ bash build-linux.sh all

#编译两次,解决第一次的问题,第一次有些目录创建存在问题
$ bash build-linux.sh all

#还是需要安装一些依赖,上面编译的库并没有完整完成依赖设置
$ sudo apt-get install luarocks

$ sudo luarocks install luasocket

$ cp deps/lib/libwx.so deps/lib/wx.so

#动态链接库应当设置LUA_CPATH而不是LUA_PATH
$ export LUA_CPATH="`pwd`/deps/lib/?.so;`pwd`/deps/lib/lua/51/?.so"

$ export LD_LIBRARY_PATH=`pwd`/deps/lib

#去掉两个检测,这两检测总是会失败,原因不好排查
$ sed -i "s/check_lua_module(wx TRUE)/#check_lua_module(wx TRUE)/g" CMakeLists.txt 

$ sed -i "s/check_lua_module(socket TRUE)/#check_lua_module(socket TRUE)/g" CMakeLists.txt

$ cmake -DCMAKE_SYSROOT=`pwd`/deps/ -DCMAKE_FIND_ROOT_PATH=`pwd`/deps/ .

$ make 

$ sudo make install

使用 Android NDK 编译 Lua

启动 Android Studio ,创建一个 Android 项目,勾选 C++ Support。

未分类

一路 Next 到这里,直接 Finish,不用勾选保持默认即可。

未分类

如果之前没有安装 NDK 和 Cmake 工具,到这里会提示安装 NDK 等工具,直接安装,我这里之前已经安装了,所以略过。

等待工程就绪之后,正常构建一次,看 Hello World 能不能正常构建运行起来。查看编译后的 APK 文件,看看里面是否有下列目录和 .so 文件。

Executing tasks: [:app:assembleDebug]
省略一堆Log
External native generate JSON debug: JSON generation completed without problems
:app:externalNativeBuildDebug
Build native-lib x86_64
[1/2] Building CXX object CMakeFiles/native-lib.dir/src/main/cpp/native-lib.cpp.o
[2/2] Linking CXX shared library ........buildintermediatescmakedebugobjx86_64libnative-lib.so
Build native-lib x86
[1/2] Building CXX object CMakeFiles/native-lib.dir/src/main/cpp/native-lib.cpp.o
[2/2] Linking CXX shared library ........buildintermediatescmakedebugobjx86libnative-lib.so
Build native-lib arm64-v8a
[1/2] Building CXX object CMakeFiles/native-lib.dir/src/main/cpp/native-lib.cpp.o
[2/2] Linking CXX shared library ........buildintermediatescmakedebugobjarm64-v8alibnative-lib.so
Build native-lib armeabi-v7a
[1/2] Building CXX object CMakeFiles/native-lib.dir/src/main/cpp/native-lib.cpp.o
[2/2] Linking CXX shared library ........buildintermediatescmakedebugobjarmeabi-v7alibnative-lib.so
:app:compileDebugSources
:app:mergeDebugShaders
:app:compileDebugShaders
:app:generateDebugAssets
:app:mergeDebugAssets
:app:transformClassesWithDexBuilderForDebug
:app:transformDexArchiveWithExternalLibsDexMergerForDebug
:app:transformDexArchiveWithDexMergerForDebug
:app:mergeDebugJniLibFolders
:app:transformNativeLibsWithMergeJniLibsForDebug
:app:transformNativeLibsWithStripDebugSymbolForDebug
:app:processDebugJavaRes NO-SOURCE
:app:transformResourcesWithMergeJavaResForDebug
:app:validateSigningDebug
:app:packageDebug
:app:assembleDebug

BUILD SUCCESSFUL in 12s
29 actionable tasks: 16 executed, 13 up-to-date

通过 7-Zip 可以查看 APK 里面的文件组成结构,如下是 lib 目录下的内容,分别对应不同的 CPU 架构。

未分类

如果能到这一步,说明至少 Android Studio 配置基本是 OK 的,NDK 能正常工作。

在 Android Studio 3.1 后,Gradle Console 就不见了,要查看构建过程和步骤,在 Build 窗口点击 Toggle View 就可以查看构建步骤了。

未分类

在 Project 面板中,CPP 目录下的文件就是 C/CPP 的源文件目录,我们从 Lua 官网下载源码后,将 .c 和 .h 文件当道这个目录下面。

未分类

目前我能下载到最新的 Lua 版本是 5.3.5 ,粘贴到 CPP 目录后,文件并不会显示,不知道这是不是 Android Studio 的 bug,接下来修改 CMakeLists.txt 里面的内容,将源代码增加到构建列表。

add_library( # Sets the name of the library.
             luajit

             # Sets the library as a shared library.
             SHARED

             # Provides a relative path to your source file(s).
             src/main/cpp/lapi.c
             src/main/cpp/lauxlib.c
             src/main/cpp/lbaselib.c
             src/main/cpp/lbitlib.c
             src/main/cpp/lcode.c
             src/main/cpp/lcorolib.c
             src/main/cpp/lctype.c
             src/main/cpp/ldblib.c
             src/main/cpp/ldebug.c
             src/main/cpp/ldo.c
             src/main/cpp/ldump.c
             src/main/cpp/lfunc.c
             src/main/cpp/lgc.c
             src/main/cpp/linit.c
             src/main/cpp/liolib.c
             src/main/cpp/llex.c
             src/main/cpp/lmathlib.c
             src/main/cpp/lmem.c
             src/main/cpp/loadlib.c
             src/main/cpp/lobject.c
             src/main/cpp/lopcodes.c
             src/main/cpp/loslib.c
             src/main/cpp/lparser.c
             src/main/cpp/lstate.c
             src/main/cpp/lstate.h
             src/main/cpp/lstring.c
             src/main/cpp/lstrlib.c
             src/main/cpp/ltable.c
             src/main/cpp/ltablib.c
             src/main/cpp/ltm.c
             src/main/cpp/lua.c
             src/main/cpp/lua.hpp
             src/main/cpp/lundump.c
             src/main/cpp/lutf8lib.c
             src/main/cpp/lvm.c
             src/main/cpp/lzio.c)

执行编译,这个时候 Build 窗口可能会出现一片红 …

未分类

具体错误内容如下:

Execution failed for task ':app:externalNativeBuildDebug'.
> Build command failed.
  Error while executing process D:ANDROIDSdkcmake3.6.4111459bincmake.exe with arguments {--build D:ANDROID_PROJECTSHelloLua2app.externalNativeBuildcmakedebugx86_64 --target luajit}
  [1/36] Building C object CMakeFiles/luajit.dir/src/main/cpp/lcode.c.o
  [2/36] Building C object CMakeFiles/luajit.dir/src/main/cpp/lcorolib.c.o
  [3/36] Building C object CMakeFiles/luajit.dir/src/main/cpp/lauxlib.c.o
  [4/36] Building C object CMakeFiles/luajit.dir/src/main/cpp/lbitlib.c.o
  [5/36] Building C object CMakeFiles/luajit.dir/src/main/cpp/lapi.c.o
  [6/36] Building C object CMakeFiles/luajit.dir/src/main/cpp/lbaselib.c.o
  [7/36] Building C object CMakeFiles/luajit.dir/src/main/cpp/loadlib.c.o
  [8/36] Building C object CMakeFiles/luajit.dir/src/main/cpp/ldo.c.o
  [9/36] Building C object CMakeFiles/luajit.dir/src/main/cpp/lgc.c.o
  [10/36] Building C object CMakeFiles/luajit.dir/src/main/cpp/ldump.c.o
  [11/36] Building C object CMakeFiles/luajit.dir/src/main/cpp/lmathlib.c.o
  [12/36] Building C object CMakeFiles/luajit.dir/src/main/cpp/liolib.c.o
  [13/36] Building C object CMakeFiles/luajit.dir/src/main/cpp/lctype.c.o
  [14/36] Building C object CMakeFiles/luajit.dir/src/main/cpp/ldebug.c.o
  [15/36] Building C object CMakeFiles/luajit.dir/src/main/cpp/linit.c.o
  [16/36] Building C object CMakeFiles/luajit.dir/src/main/cpp/ldblib.c.o
  [17/36] Building C object CMakeFiles/luajit.dir/src/main/cpp/llex.c.o
  [18/36] Building C object CMakeFiles/luajit.dir/src/main/cpp/lfunc.c.o
  [19/36] Building C object CMakeFiles/luajit.dir/src/main/cpp/lmem.c.o
  [20/36] Building C object CMakeFiles/luajit.dir/src/main/cpp/lobject.c.o
  [21/36] Building C object CMakeFiles/luajit.dir/src/main/cpp/ltm.c.o
  [22/36] Building C object CMakeFiles/luajit.dir/src/main/cpp/lopcodes.c.o
  [23/36] Building C object CMakeFiles/luajit.dir/src/main/cpp/lstate.c.o
  [24/36] Building C object CMakeFiles/luajit.dir/src/main/cpp/lstrlib.c.o
  [25/36] Building C object CMakeFiles/luajit.dir/src/main/cpp/ltablib.c.o
  [26/36] Building C object CMakeFiles/luajit.dir/src/main/cpp/lparser.c.o
  [27/36] Building C object CMakeFiles/luajit.dir/src/main/cpp/lua.c.o
  [28/36] Building C object CMakeFiles/luajit.dir/src/main/cpp/luac.c.o
  [29/36] Building C object CMakeFiles/luajit.dir/src/main/cpp/lstring.c.o
  [30/36] Building C object CMakeFiles/luajit.dir/src/main/cpp/lundump.c.o
  [31/36] Building C object CMakeFiles/luajit.dir/src/main/cpp/lutf8lib.c.o
  [32/36] Building C object CMakeFiles/luajit.dir/src/main/cpp/lvm.c.o
  [33/36] Building C object CMakeFiles/luajit.dir/src/main/cpp/ltable.c.o
  [34/36] Building C object CMakeFiles/luajit.dir/src/main/cpp/loslib.c.o
  [35/36] Building C object CMakeFiles/luajit.dir/src/main/cpp/lzio.c.o
  D:ANDROID_PROJECTSHelloLua2appsrcmaincpploslib.c:169:3: warning: 'tmpnam' is deprecated: tempnam is unsafe, use mkstemp or tmpfile instead [-Wdeprecated-declarations]
    lua_tmpnam(buff, err);
    ^
  D:ANDROID_PROJECTSHelloLua2appsrcmaincpploslib.c:131:33: note: expanded from macro 'lua_tmpnam'
  #define lua_tmpnam(b,e)         { e = (tmpnam(b) == NULL); }
                                         ^
  D:/ANDROID/Sdk/ndk-bundle/sysroot/usr/includestdio.h:167:5: note: 'tmpnam' has been explicitly marked deprecated here
      __warnattr("tempnam is unsafe, use mkstemp or tmpfile instead");
      ^
  D:/ANDROID/Sdk/ndk-bundle/sysroot/usr/includesys/cdefs.h:160:40: note: expanded from macro '__warnattr'
  #define __warnattr(msg) __attribute__((deprecated(msg)))
                                         ^
  1 warning generated.
  [36/36] Linking C shared library ........buildintermediatescmakedebugobjx86_64libluajit.so
  FAILED: cmd.exe /C "cd . && D:ANDROIDSdkndk-bundletoolchainsllvmprebuiltwindows-x86_64binclang.exe  --target=x86_64-none-linux-android21 --gcc-toolchain=D:/ANDROID/Sdk/ndk-bundle/toolchains/x86_64-4.9/prebuilt/windows-x86_64 --sysroot=D:/ANDROID/Sdk/ndk-bundle/sysroot -fPIC -isystem D:/ANDROID/Sdk/ndk-bundle/sysroot/usr/include/x86_64-linux-android -g -DANDROID -ffunction-sections -funwind-tables -fstack-protector-strong -no-canonical-prefixes -Wa,--noexecstack -Wformat -Werror=format-security  -O0 -fno-limit-debug-info  -Wl,--exclude-libs,libgcc.a -Wl,--exclude-libs,libatomic.a -nostdlib++ --sysroot D:/ANDROID/Sdk/ndk-bundle/platforms/android-21/arch-x86_64 -Wl,--build-id -Wl,--warn-shared-textrel -Wl,--fatal-warnings -LD:/ANDROID/Sdk/ndk-bundle/sources/cxx-stl/llvm-libc++/libs/x86_64 -Wl,--no-undefined -Wl,-z,noexecstack -Qunused-arguments -Wl,-z,relro -Wl,-z,now -shared -Wl,-soname,libluajit.so -o ........buildintermediatescmakedebugobjx86_64libluajit.so CMakeFiles/luajit.dir/src/main/cpp/lapi.c.o CMakeFiles/luajit.dir/src/main/cpp/lauxlib.c.o CMakeFiles/luajit.dir/src/main/cpp/lbaselib.c.o CMakeFiles/luajit.dir/src/main/cpp/lbitlib.c.o CMakeFiles/luajit.dir/src/main/cpp/lcode.c.o CMakeFiles/luajit.dir/src/main/cpp/lcorolib.c.o CMakeFiles/luajit.dir/src/main/cpp/lctype.c.o CMakeFiles/luajit.dir/src/main/cpp/ldblib.c.o CMakeFiles/luajit.dir/src/main/cpp/ldebug.c.o CMakeFiles/luajit.dir/src/main/cpp/ldo.c.o CMakeFiles/luajit.dir/src/main/cpp/ldump.c.o CMakeFiles/luajit.dir/src/main/cpp/lfunc.c.o CMakeFiles/luajit.dir/src/main/cpp/lgc.c.o CMakeFiles/luajit.dir/src/main/cpp/linit.c.o CMakeFiles/luajit.dir/src/main/cpp/liolib.c.o CMakeFiles/luajit.dir/src/main/cpp/llex.c.o CMakeFiles/luajit.dir/src/main/cpp/lmathlib.c.o CMakeFiles/luajit.dir/src/main/cpp/lmem.c.o CMakeFiles/luajit.dir/src/main/cpp/loadlib.c.o CMakeFiles/luajit.dir/src/main/cpp/lobject.c.o CMakeFiles/luajit.dir/src/main/cpp/lopcodes.c.o CMakeFiles/luajit.dir/src/main/cpp/loslib.c.o CMakeFiles/luajit.dir/src/main/cpp/lparser.c.o CMakeFiles/luajit.dir/src/main/cpp/lstate.c.o CMakeFiles/luajit.dir/src/main/cpp/lstring.c.o CMakeFiles/luajit.dir/src/main/cpp/lstrlib.c.o CMakeFiles/luajit.dir/src/main/cpp/ltable.c.o CMakeFiles/luajit.dir/src/main/cpp/ltablib.c.o CMakeFiles/luajit.dir/src/main/cpp/ltm.c.o CMakeFiles/luajit.dir/src/main/cpp/lua.c.o CMakeFiles/luajit.dir/src/main/cpp/luac.c.o CMakeFiles/luajit.dir/src/main/cpp/lundump.c.o CMakeFiles/luajit.dir/src/main/cpp/lutf8lib.c.o CMakeFiles/luajit.dir/src/main/cpp/lvm.c.o CMakeFiles/luajit.dir/src/main/cpp/lzio.c.o  -llog -latomic -lm && cd ."
  D:/ANDROID/Sdk/ndk-bundle/toolchains/x86_64-4.9/prebuilt/windows-x86_64/lib/gcc/x86_64-linux-android/4.9.x/../../../../x86_64-linux-android/binld: error: CMakeFiles/luajit.dir/src/main/cpp/luac.c.o: multiple definition of 'main'
  D:/ANDROID/Sdk/ndk-bundle/toolchains/x86_64-4.9/prebuilt/windows-x86_64/lib/gcc/x86_64-linux-android/4.9.x/../../../../x86_64-linux-android/binld: CMakeFiles/luajit.dir/src/main/cpp/lua.c.o: previous definition here
  clang.exe: error: linker command failed with exit code 1 (use -v to see invocation)
  ninja: build stopped: subcommand failed.


* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights.

* Get more help at https://help.gradle.org

BUILD FAILED in 9s
21 actionable tasks: 18 executed, 3 up-to-date

提示中说的也比较清楚,我们使用了已经废弃了的函数:warning: ‘tmpnam’ is deprecated: tempnam is unsafe, use mkstemp or tmpfile instead,但是项目中,使用这个 tmpnam 的比较多,而且需要改 Lua 源代码,本着不对 Lua 改动的原则,我们在 CMakeLists.txt 中增加下面的一行代码,屏蔽废弃函数的错误警告。

add_definitions(-Wno-deprecated)

最终,文件结构如下:

未分类

继续编译,发现依然编译不通过,Build 窗口依然一片红,如下:

Executing tasks: [clean, :app:assembleDebug]
省略一堆Log
* What went wrong:
Execution failed for task ':app:externalNativeBuildDebug'.
> Build command failed.
  Error while executing process D:ANDROIDSdkcmake3.6.4111459bincmake.exe with arguments {--build D:ANDROID_PROJECTSHelloLua2app.externalNativeBuildcmakedebugx86_64 --target luajit}
  Recompacting log...
  [1/36] Building C object CMakeFiles/luajit.dir/src/main/cpp/lcorolib.c.o
  [2/36] Building C object CMakeFiles/luajit.dir/src/main/cpp/lapi.c.o
  [3/36] Building C object CMakeFiles/luajit.dir/src/main/cpp/lbaselib.c.o
  [4/36] Building C object CMakeFiles/luajit.dir/src/main/cpp/lbitlib.c.o
  [5/36] Building C object CMakeFiles/luajit.dir/src/main/cpp/lcode.c.o
  [6/36] Building C object CMakeFiles/luajit.dir/src/main/cpp/lauxlib.c.o
  [7/36] Building C object CMakeFiles/luajit.dir/src/main/cpp/ldebug.c.o
  [8/36] Building C object CMakeFiles/luajit.dir/src/main/cpp/lfunc.c.o
  [9/36] Building C object CMakeFiles/luajit.dir/src/main/cpp/linit.c.o
  [10/36] Building C object CMakeFiles/luajit.dir/src/main/cpp/liolib.c.o
  [11/36] Building C object CMakeFiles/luajit.dir/src/main/cpp/lctype.c.o
  [12/36] Building C object CMakeFiles/luajit.dir/src/main/cpp/lgc.c.o
  [13/36] Building C object CMakeFiles/luajit.dir/src/main/cpp/llex.c.o
  [14/36] Building C object CMakeFiles/luajit.dir/src/main/cpp/ldblib.c.o
  [15/36] Building C object CMakeFiles/luajit.dir/src/main/cpp/ldo.c.o
  [16/36] Building C object CMakeFiles/luajit.dir/src/main/cpp/lmem.c.o
  [17/36] Building C object CMakeFiles/luajit.dir/src/main/cpp/loadlib.c.o
  [18/36] Building C object CMakeFiles/luajit.dir/src/main/cpp/lmathlib.c.o
  [19/36] Building C object CMakeFiles/luajit.dir/src/main/cpp/ldump.c.o
  [20/36] Building C object CMakeFiles/luajit.dir/src/main/cpp/lobject.c.o
  [21/36] Building C object CMakeFiles/luajit.dir/src/main/cpp/lstrlib.c.o
  [22/36] Building C object CMakeFiles/luajit.dir/src/main/cpp/loslib.c.o
  [23/36] Building C object CMakeFiles/luajit.dir/src/main/cpp/lopcodes.c.o
  [24/36] Building C object CMakeFiles/luajit.dir/src/main/cpp/ltable.c.o
  [25/36] Building C object CMakeFiles/luajit.dir/src/main/cpp/lua.c.o
  [26/36] Building C object CMakeFiles/luajit.dir/src/main/cpp/lundump.c.o
  [27/36] Building C object CMakeFiles/luajit.dir/src/main/cpp/lparser.c.o
  [28/36] Building C object CMakeFiles/luajit.dir/src/main/cpp/lstring.c.o
  [29/36] Building C object CMakeFiles/luajit.dir/src/main/cpp/ltablib.c.o
  [30/36] Building C object CMakeFiles/luajit.dir/src/main/cpp/ltm.c.o
  [31/36] Building C object CMakeFiles/luajit.dir/src/main/cpp/lstate.c.o
  [32/36] Building C object CMakeFiles/luajit.dir/src/main/cpp/lutf8lib.c.o
  [33/36] Building C object CMakeFiles/luajit.dir/src/main/cpp/lvm.c.o
  [34/36] Building C object CMakeFiles/luajit.dir/src/main/cpp/luac.c.o
  [35/36] Building C object CMakeFiles/luajit.dir/src/main/cpp/lzio.c.o
  [36/36] Linking C shared library ........buildintermediatescmakedebugobjx86_64libluajit.so
  FAILED: cmd.exe /C "cd . && D:ANDROIDSdkndk-bundletoolchainsllvmprebuiltwindows-x86_64binclang.exe  --target=x86_64-none-linux-android21 --gcc-toolchain=D:/ANDROID/Sdk/ndk-bundle/toolchains/x86_64-4.9/prebuilt/windows-x86_64 --sysroot=D:/ANDROID/Sdk/ndk-bundle/sysroot -fPIC -isystem D:/ANDROID/Sdk/ndk-bundle/sysroot/usr/include/x86_64-linux-android -g -DANDROID -ffunction-sections -funwind-tables -fstack-protector-strong -no-canonical-prefixes -Wa,--noexecstack -Wformat -Werror=format-security  -O0 -fno-limit-debug-info  -Wl,--exclude-libs,libgcc.a -Wl,--exclude-libs,libatomic.a -nostdlib++ --sysroot D:/ANDROID/Sdk/ndk-bundle/platforms/android-21/arch-x86_64 -Wl,--build-id -Wl,--warn-shared-textrel -Wl,--fatal-warnings -LD:/ANDROID/Sdk/ndk-bundle/sources/cxx-stl/llvm-libc++/libs/x86_64 -Wl,--no-undefined -Wl,-z,noexecstack -Qunused-arguments -Wl,-z,relro -Wl,-z,now -shared -Wl,-soname,libluajit.so -o ........buildintermediatescmakedebugobjx86_64libluajit.so CMakeFiles/luajit.dir/src/main/cpp/lapi.c.o CMakeFiles/luajit.dir/src/main/cpp/lauxlib.c.o CMakeFiles/luajit.dir/src/main/cpp/lbaselib.c.o CMakeFiles/luajit.dir/src/main/cpp/lbitlib.c.o CMakeFiles/luajit.dir/src/main/cpp/lcode.c.o CMakeFiles/luajit.dir/src/main/cpp/lcorolib.c.o CMakeFiles/luajit.dir/src/main/cpp/lctype.c.o CMakeFiles/luajit.dir/src/main/cpp/ldblib.c.o CMakeFiles/luajit.dir/src/main/cpp/ldebug.c.o CMakeFiles/luajit.dir/src/main/cpp/ldo.c.o CMakeFiles/luajit.dir/src/main/cpp/ldump.c.o CMakeFiles/luajit.dir/src/main/cpp/lfunc.c.o CMakeFiles/luajit.dir/src/main/cpp/lgc.c.o CMakeFiles/luajit.dir/src/main/cpp/linit.c.o CMakeFiles/luajit.dir/src/main/cpp/liolib.c.o CMakeFiles/luajit.dir/src/main/cpp/llex.c.o CMakeFiles/luajit.dir/src/main/cpp/lmathlib.c.o CMakeFiles/luajit.dir/src/main/cpp/lmem.c.o CMakeFiles/luajit.dir/src/main/cpp/loadlib.c.o CMakeFiles/luajit.dir/src/main/cpp/lobject.c.o CMakeFiles/luajit.dir/src/main/cpp/lopcodes.c.o CMakeFiles/luajit.dir/src/main/cpp/loslib.c.o CMakeFiles/luajit.dir/src/main/cpp/lparser.c.o CMakeFiles/luajit.dir/src/main/cpp/lstate.c.o CMakeFiles/luajit.dir/src/main/cpp/lstring.c.o CMakeFiles/luajit.dir/src/main/cpp/lstrlib.c.o CMakeFiles/luajit.dir/src/main/cpp/ltable.c.o CMakeFiles/luajit.dir/src/main/cpp/ltablib.c.o CMakeFiles/luajit.dir/src/main/cpp/ltm.c.o CMakeFiles/luajit.dir/src/main/cpp/lua.c.o CMakeFiles/luajit.dir/src/main/cpp/luac.c.o CMakeFiles/luajit.dir/src/main/cpp/lundump.c.o CMakeFiles/luajit.dir/src/main/cpp/lutf8lib.c.o CMakeFiles/luajit.dir/src/main/cpp/lvm.c.o CMakeFiles/luajit.dir/src/main/cpp/lzio.c.o  -llog -latomic -lm && cd ."
  D:/ANDROID/Sdk/ndk-bundle/toolchains/x86_64-4.9/prebuilt/windows-x86_64/lib/gcc/x86_64-linux-android/4.9.x/../../../../x86_64-linux-android/binld: error: CMakeFiles/luajit.dir/src/main/cpp/luac.c.o: multiple definition of 'main'
  D:/ANDROID/Sdk/ndk-bundle/toolchains/x86_64-4.9/prebuilt/windows-x86_64/lib/gcc/x86_64-linux-android/4.9.x/../../../../x86_64-linux-android/binld: CMakeFiles/luajit.dir/src/main/cpp/lua.c.o: previous definition here
  clang.exe: error: linker command failed with exit code 1 (use -v to see invocation)
  ninja: build stopped: subcommand failed.


* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights.

* Get more help at https://help.gradle.org

BUILD FAILED in 9s
21 actionable tasks: 19 executed, 2 up-to-date

在这个错误里,有两处非常明显:

error: CMakeFiles/luajit.dir/src/main/cpp/luac.c.o: multiple definition of 'main'
CMakeFiles/luajit.dir/src/main/cpp/lua.c.o: previous definition here

事实上,我们也不需要 Main 函数的定义,因为编译为 SHARE_LIBRARY ,所以屏蔽这个文件即可。

未分类

再次构建,发现还是一片红… 错误如下:

省略一堆Log
Execution failed for task ':app:externalNativeBuildDebug'.
> Build command failed.
  Error while executing process D:ANDROIDSdkcmake3.6.4111459bincmake.exe with arguments {--build D:ANDROID_PROJECTSHelloLua2app.externalNativeBuildcmakedebugx86 --target luajit}
  [1/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lbaselib.c.o
  [2/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lbitlib.c.o
  [3/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lcode.c.o
  [4/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lauxlib.c.o
  [5/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lcorolib.c.o
  [6/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lapi.c.o
  [7/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lgc.c.o
  [8/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/loadlib.c.o
  [9/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lobject.c.o
  [10/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/ldblib.c.o
  [11/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lctype.c.o
  [12/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/ldump.c.o
  [13/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/llex.c.o
  [14/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/liolib.c.o
  [15/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/ldo.c.o
  [16/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lmathlib.c.o
  [17/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lmem.c.o
  [18/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/linit.c.o
  [19/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lfunc.c.o
  [20/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/ldebug.c.o
  [21/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lvm.c.o
  [22/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lutf8lib.c.o
  [23/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/loslib.c.o
  [24/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lstrlib.c.o
  [25/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lzio.c.o
  [26/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lparser.c.o
  [27/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lua.c.o
  [28/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lstring.c.o
  [29/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/ltm.c.o
  [30/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lopcodes.c.o
  [31/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lstate.c.o
  [32/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/ltablib.c.o
  [33/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lundump.c.o
  [34/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/ltable.c.o
  [35/35] Linking C shared library ........buildintermediatescmakedebugobjx86libluajit.so
  FAILED: cmd.exe /C "cd . && D:ANDROIDSdkndk-bundletoolchainsllvmprebuiltwindows-x86_64binclang.exe  --target=i686-none-linux-android19 --gcc-toolchain=D:/ANDROID/Sdk/ndk-bundle/toolchains/x86-4.9/prebuilt/windows-x86_64 --sysroot=D:/ANDROID/Sdk/ndk-bundle/sysroot -fPIC -isystem D:/ANDROID/Sdk/ndk-bundle/sysroot/usr/include/i686-linux-android -g -DANDROID -ffunction-sections -funwind-tables -fstack-protector-strong -no-canonical-prefixes -mstackrealign -Wa,--noexecstack -Wformat -Werror=format-security  -O0 -fno-limit-debug-info  -Wl,--exclude-libs,libgcc.a -Wl,--exclude-libs,libatomic.a -nostdlib++ --sysroot D:/ANDROID/Sdk/ndk-bundle/platforms/android-19/arch-x86 -Wl,--build-id -Wl,--warn-shared-textrel -Wl,--fatal-warnings -LD:/ANDROID/Sdk/ndk-bundle/sources/cxx-stl/llvm-libc++/libs/x86 -Wl,--no-undefined -Wl,-z,noexecstack -Qunused-arguments -Wl,-z,relro -Wl,-z,now -shared -Wl,-soname,libluajit.so -o ........buildintermediatescmakedebugobjx86libluajit.so CMakeFiles/luajit.dir/src/main/cpp/lapi.c.o CMakeFiles/luajit.dir/src/main/cpp/lauxlib.c.o CMakeFiles/luajit.dir/src/main/cpp/lbaselib.c.o CMakeFiles/luajit.dir/src/main/cpp/lbitlib.c.o CMakeFiles/luajit.dir/src/main/cpp/lcode.c.o CMakeFiles/luajit.dir/src/main/cpp/lcorolib.c.o CMakeFiles/luajit.dir/src/main/cpp/lctype.c.o CMakeFiles/luajit.dir/src/main/cpp/ldblib.c.o CMakeFiles/luajit.dir/src/main/cpp/ldebug.c.o CMakeFiles/luajit.dir/src/main/cpp/ldo.c.o CMakeFiles/luajit.dir/src/main/cpp/ldump.c.o CMakeFiles/luajit.dir/src/main/cpp/lfunc.c.o CMakeFiles/luajit.dir/src/main/cpp/lgc.c.o CMakeFiles/luajit.dir/src/main/cpp/linit.c.o CMakeFiles/luajit.dir/src/main/cpp/liolib.c.o CMakeFiles/luajit.dir/src/main/cpp/llex.c.o CMakeFiles/luajit.dir/src/main/cpp/lmathlib.c.o CMakeFiles/luajit.dir/src/main/cpp/lmem.c.o CMakeFiles/luajit.dir/src/main/cpp/loadlib.c.o CMakeFiles/luajit.dir/src/main/cpp/lobject.c.o CMakeFiles/luajit.dir/src/main/cpp/lopcodes.c.o CMakeFiles/luajit.dir/src/main/cpp/loslib.c.o CMakeFiles/luajit.dir/src/main/cpp/lparser.c.o CMakeFiles/luajit.dir/src/main/cpp/lstate.c.o CMakeFiles/luajit.dir/src/main/cpp/lstring.c.o CMakeFiles/luajit.dir/src/main/cpp/lstrlib.c.o CMakeFiles/luajit.dir/src/main/cpp/ltable.c.o CMakeFiles/luajit.dir/src/main/cpp/ltablib.c.o CMakeFiles/luajit.dir/src/main/cpp/ltm.c.o CMakeFiles/luajit.dir/src/main/cpp/lua.c.o CMakeFiles/luajit.dir/src/main/cpp/lundump.c.o CMakeFiles/luajit.dir/src/main/cpp/lutf8lib.c.o CMakeFiles/luajit.dir/src/main/cpp/lvm.c.o CMakeFiles/luajit.dir/src/main/cpp/lzio.c.o  -llog -latomic -lm && cd ."
  D:ANDROID_PROJECTSHelloLua2appsrcmaincpp/liolib.c:446: error: undefined reference to 'localeconv'
  D:ANDROID_PROJECTSHelloLua2appsrcmaincpp/lobject.c:287: error: undefined reference to 'localeconv'
  D:ANDROID_PROJECTSHelloLua2appsrcmaincpp/lobject.c:381: error: undefined reference to 'localeconv'
  D:ANDROID_PROJECTSHelloLua2appsrcmaincpp/lstrlib.c:936: error: undefined reference to 'localeconv'
  clang.exe: error: linker command failed with exit code 1 (use -v to see invocation)
  ninja: build stopped: subcommand failed.


* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights.

* Get more help at https://help.gradle.org

BUILD FAILED in 12s
21 actionable tasks: 18 executed, 3 up-to-date

这次的错误都是这一个:undefined reference to ‘localeconv’,解决办法是提高 API Level ,我们把它改为 21 以上就可以解决这个问题。

再次构建,终于不再报错了,结果如下:

Executing tasks: [clean, :app:assembleDebug]
省略一堆Log
:app:externalNativeBuildCleanDebug
Clean luajit armeabi-v7a
Cleaning... 0 files.
Clean luajit arm64-v8a
Cleaning... 0 files.
Clean luajit x86
Cleaning... 0 files.
Clean luajit x86_64
Cleaning... 35 files.
:app:externalNativeBuildCleanRelease
Clean luajit armeabi-v7a
Cleaning... 0 files.
Clean luajit arm64-v8a
Cleaning... 0 files.
Clean luajit x86
Cleaning... 0 files.
Clean luajit x86_64
Cleaning... 0 files.
:app:clean
:app:preBuild UP-TO-DATE
:app:preDebugBuild
:app:compileDebugAidl
:app:compileDebugRenderscript
:app:checkDebugManifest
:app:generateDebugBuildConfig
:app:prepareLintJar UP-TO-DATE
:app:mainApkListPersistenceDebug
:app:generateDebugResValues
:app:generateDebugResources
:app:mergeDebugResources
:app:createDebugCompatibleScreenManifests
:app:processDebugManifest
:app:splitsDiscoveryTaskDebug
:app:processDebugResources
:app:generateDebugSources
:app:javaPreCompileDebug
:app:compileDebugJavaWithJavac
:app:generateJsonModelDebug UP-TO-DATE
:app:externalNativeBuildDebug
Build luajit x86_64
Recompacting log...
[1/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lcorolib.c.o
[2/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lauxlib.c.o
[3/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lbaselib.c.o
[4/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lbitlib.c.o
[5/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lcode.c.o
[6/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lapi.c.o
[7/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lctype.c.o
[8/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lmathlib.c.o
[9/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/liolib.c.o
[10/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lfunc.c.o
[11/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lmem.c.o
[12/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/loadlib.c.o
[13/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/linit.c.o
[14/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/ldblib.c.o
[15/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lobject.c.o
[16/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/llex.c.o
[17/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/ldebug.c.o
[18/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lgc.c.o
[19/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/ldo.c.o
[20/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/ldump.c.o
[21/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lzio.c.o
[22/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lstring.c.o
[23/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/loslib.c.o
[24/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/ltable.c.o
[25/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lundump.c.o
[26/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lopcodes.c.o
[27/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/ltm.c.o
[28/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lstate.c.o
[29/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lparser.c.o
[30/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/ltablib.c.o
[31/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lstrlib.c.o
[32/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lutf8lib.c.o
[33/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lvm.c.o
[34/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lua.c.o
[35/35] Linking C shared library ........buildintermediatescmakedebugobjx86_64libluajit.so
Build luajit x86
[1/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lcorolib.c.o
[2/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lapi.c.o
[3/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lbaselib.c.o
[4/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lbitlib.c.o
[5/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lauxlib.c.o
[6/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lcode.c.o
[7/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lmem.c.o
[8/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/loadlib.c.o
[9/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/ldo.c.o
[10/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lctype.c.o
[11/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lobject.c.o
[12/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/liolib.c.o
[13/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/llex.c.o
[14/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/linit.c.o
[15/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/ldebug.c.o
[16/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lfunc.c.o
[17/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lgc.c.o
[18/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/ldblib.c.o
[19/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/ldump.c.o
[20/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lmathlib.c.o
[21/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lutf8lib.c.o
[22/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lstate.c.o
[23/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lvm.c.o
[24/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lzio.c.o
[25/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lparser.c.o
[26/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lstrlib.c.o
[27/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/ltable.c.o
[28/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/ltm.c.o
[29/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/ltablib.c.o
[30/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lstring.c.o
[31/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lopcodes.c.o
[32/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lua.c.o
[33/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lundump.c.o
[34/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/loslib.c.o
[35/35] Linking C shared library ........buildintermediatescmakedebugobjx86libluajit.so
Build luajit arm64-v8a
[1/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lbitlib.c.o
[2/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lauxlib.c.o
[3/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lbaselib.c.o
[4/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lcode.c.o
[5/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lcorolib.c.o
[6/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lapi.c.o
[7/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lfunc.c.o
[8/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/liolib.c.o
[9/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/linit.c.o
[10/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/loadlib.c.o
[11/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lobject.c.o
[12/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/ldump.c.o
[13/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/ldebug.c.o
[14/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/ldblib.c.o
[15/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/ldo.c.o
[16/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/llex.c.o
[17/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lctype.c.o
[18/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lmathlib.c.o
[19/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lmem.c.o
[20/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lgc.c.o
[21/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lstring.c.o
[22/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lvm.c.o
[23/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/ltablib.c.o
[24/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lzio.c.o
[25/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lua.c.o
[26/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lstate.c.o
[27/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lstrlib.c.o
[28/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/ltable.c.o
[29/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lutf8lib.c.o
[30/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/loslib.c.o
[31/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lundump.c.o
[32/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lopcodes.c.o
[33/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/ltm.c.o
[34/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lparser.c.o
[35/35] Linking C shared library ........buildintermediatescmakedebugobjarm64-v8alibluajit.so
Build luajit armeabi-v7a
[1/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lcorolib.c.o
[2/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lbitlib.c.o
[3/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lapi.c.o
[4/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lauxlib.c.o
[5/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lbaselib.c.o
[6/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lcode.c.o
[7/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/ldo.c.o
[8/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/llex.c.o
[9/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/ldump.c.o
[10/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lobject.c.o
[11/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/liolib.c.o
[12/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lmathlib.c.o
[13/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lmem.c.o
[14/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/loadlib.c.o
[15/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/ldebug.c.o
[16/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lfunc.c.o
[17/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/ldblib.c.o
[18/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lgc.c.o
[19/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/linit.c.o
[20/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lctype.c.o
[21/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lua.c.o
[22/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lopcodes.c.o
[23/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/ltm.c.o
[24/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/loslib.c.o
[25/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lundump.c.o
[26/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/ltable.c.o
[27/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lstring.c.o
[28/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lutf8lib.c.o
[29/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lzio.c.o
[30/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lstate.c.o
[31/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lvm.c.o
[32/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/ltablib.c.o
[33/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lstrlib.c.o
[34/35] Building C object CMakeFiles/luajit.dir/src/main/cpp/lparser.c.o
[35/35] Linking C shared library ........buildintermediatescmakedebugobjarmeabi-v7alibluajit.so
:app:compileDebugSources
:app:mergeDebugShaders
:app:compileDebugShaders
:app:generateDebugAssets
:app:mergeDebugAssets
:app:transformClassesWithDexBuilderForDebug
:app:transformDexArchiveWithExternalLibsDexMergerForDebug
:app:transformDexArchiveWithDexMergerForDebug
:app:mergeDebugJniLibFolders
:app:transformNativeLibsWithMergeJniLibsForDebug
:app:transformNativeLibsWithStripDebugSymbolForDebug
:app:processDebugJavaRes NO-SOURCE
:app:transformResourcesWithMergeJavaResForDebug
:app:validateSigningDebug
:app:packageDebug
:app:assembleDebug

BUILD SUCCESSFUL in 20s
33 actionable tasks: 30 executed, 3 up-to-date

再生成 APK,使用 7-ZIP 查看 APK,就可以看到我们 Lua 已经稳妥妥的在 APK 安装包中了,达到 340 KB了,对于 so 库来说也不小了。

未分类

至此,Lua 已经编译好了,如果需要使用,还需要写 JNI 方法调用,不过那是后话了。

Github 地址:https://github.com/yahch/Luandroid

lua 三元运算符的使用

C 语言中有 a?b:c 的方法,用来判断,如果a 为真,则结果为b,如果a为假,则结果为c

下面用一个比较的函数来说明lua中三元运算符的使用

-- lua 三元运算符的使用 类似C中的 a > b ? c 
function compare1(a,b) 
    if a > b then 
        return a 
    else 
        return b
    end
end

function mcompare(a,b) 
    return ( a>b and a ) or b
end

print(compare1(1,2)) -->2
print(mcompare(1,2)) -->2

由上可以看书,lua中三元运算符用 (a and b) or c 来表示

【网页加速】lua redis的二次升级

之前发过openresty的相关文章,也是用于加速网页速度的,但是上次没有优化好代码,这次整理了下,优化了nginx的配置和lua的代码,感兴趣的话可以看看上篇的文章:
https://www.cnblogs.com/w1570631036/p/8449373.html

为了学习,不断的给自己的服务器装东西,又是logstash,又是kafka,导致主站网络负载、cpu消耗过大,再加上tomcat这个本身就特别占用内存的东西,只要稍微刷新一下网站,就能感受到蜗牛般的速度,实在受不了,前段时间给网站加了n多层缓存,依旧没有改观多少,想了想,算了,一直都这么卡,还不如直接将动态的网站直接变成静态网页存储在redis里面,然后关掉tomcat,貌似没有改观多少,但是在xshell里面敲命令没那么卡了,这里,也提出了一种别样的网站加速方法——redis存储静态网页。
未分类

一、总体流程如下

未分类
1.一次请求过来,通过openresty的nginx来访问lua脚本;
2.读取redis中是否存在该uri对应的静态网页,如果有,则直接返回,否则回源到tomcat,然后将响应的内容保存到redis里面。

二、nginx的设置

openresty中自带了nginx,所以只需要配置一下即可,我们最终的目前是拦截所有以html结尾的请求,如果是以其他后缀结尾的,比如do,则可以直接回滚到tomat里面去。
由于篇幅的关系,只粘贴部分nginx配置,想看全的请转至:mynginxconfig.ngx

server {
    listen       80;
    # listen       443 ssl;   # ssl
    server_name  www.wenzhihuai.com;
    location  ~ .*.(html)$ {       //拦截所有以html结尾的请求,调用lua脚本
        ...
        charset utf8;
        proxy_pass_request_headers off ;
        # 关闭缓存lua脚本,调试的时候专用
        lua_code_cache off;
        content_by_lua_file /opt/lua/hello.lua;
    }
    location / {        //nginx是按顺序匹配的,如果上面的不符合,那么将回滚tomcat
        default_type    text/html;
        root   html;
        index  index.html index.htm;
        ...
        # websocket
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_pass http://backend;
    }

三、lua脚本

为了方便key的操作,经过测试,即使uri带有各种字符,比如 ? . html = &等,都是可以直接设置为redis中的key的,所以,不是那么的需要考虑redis的key违反规则,可以直接将uri设置为key。具体流程如下:

local key = request_uri
首先,key为请求访问的uri
local resp, err = red:get(key)
去redis上查找有没有
if resp == ngx.null then
    如果没有
    ngx.req.set_header("Accept", "text/html,application/xhtml+xml,application/xml;")
    ngx.req.set_header("Accept-Encoding", "")
    这里,特别需要注意的是,要把头部的信息去掉,这里之前说过。(如果不去掉,就是gzip加密返回,然后再经过一层gzip加密返回给用户,导致用户看到的是gzip压缩过的乱码)
    local targetURL = string.gsub(uri, "html", "do")
    这里讲html替换为do,即:不拦截*.do的请求,其可以直接访问tomcat
    local respp = ngx.location.capture(targetURL, { method = ngx.HTTP_GET, args = uri_args })
    开始回源到tomcat
    red:set(key, respp.body)
    将uri(key)和响应的内容设到redis里面去
    red:expire(key, 600)
    lua redis并没有提供在set的时候同时设置过期时间,所以,额外加一行设置过期时间
    ngx.print(respp.body)
    将响应的内容输出给用户
    return
end
ngx.print(resp)

四、测试

进行一次测试,以访问http://www.wenzhihuai.com/jaowejoifjefoijoifaew.html 为例,我的网站并没有设置这个uri,所以,访问的时候,会统一调到错误页面,之后,会在redis中看到有这条记录:
未分类
该地址已经成功被缓存到redis里面去,点击其他页面,可以看到,只要是点击的页面,都被缓存到redis里面去了。总体来说,如果不设置过期时间,可以把整个网页静态化缓存到redis里面,甚至是可以关闭tomcat了,但是这种做法只适用于万年不变的页面,至于用于企业的话,,,,

后记:
其实我有个疑问,我的代码里,并没有设置lua断开redis的连接,不知道会不会有影响,而且它这个是指每次请求过来,都需要重新连接redis么?光是TCP三次握手就耗时不少啊,不知道怎么优化这些信息。

全部代码如下:

local redis = require "resty.redis"
local red = redis:new()
local request_uri = ngx.var.request_uri
local ngx_log = ngx.log
local ngx_ERR = ngx.ERR

local function close_redis(red)
    if not red then
        return
    end
    local pool_max_idle_time = 10000
    local pool_size = 100
    red:set("pool_size", pool_size)
    local ok, err = red:set_keepalive(pool_max_idle_time, pool_size)
    if not ok then
        ngx_log(ngx_ERR, "set redis keepalive error : ", err)
    end
end

local uri = ngx.var.uri

red:set_timeout(1000)
red:connect("119.23.46.71", 6340)
red:auth("root")
local uri_args = ngx.req.get_uri_args()

local key = request_uri
local resp, err = red:get(key)

if resp == ngx.null then
    ngx.req.set_header("Accept", "text/html,application/xhtml+xml,application/xml;")
    ngx.req.set_header("Accept-Encoding", "")
    local targetURL = string.gsub(uri, "html", "do")
    local respp = ngx.location.capture(targetURL, { method = ngx.HTTP_GET, args = uri_args })
    red:set(key, respp.body)
    red:expire(key, 600)
    ngx.print(respp.body)
    return
end
ngx.print(resp)
close_redis(red)

基于Nginx+lua的蓝绿发布系统

一、什么是蓝绿发布

蓝绿部署是不停老版本,部署新版本然后进行测试,确认OK,将流量切到新版本,然后老版本同时也升级到新版本。

1、特点

蓝绿部署无需停机,并且风险较小。

2、蓝绿发布的注意事项

当你切换到蓝色环境时,需要妥当处理未完成的业务和新的业务。如果你的数据库后端无法处理,会是一个比较麻烦的问题;

可能会出现需要同时处理“微服务架构应用”和“传统架构应用”的情况,如果在蓝绿部署中协调不好这两者,还是有可能会导致服务停止。
需要提前考虑数据库与应用部署同步迁移 /回滚的问题。
蓝绿部署需要有基础设施支持。
在非隔离基础架构( VM 、 Docker 等)上执行蓝绿部署,蓝色环境和绿色环境有被摧毁的风险。

二、为什么需要蓝绿发布系统

1、新项目和新需求非常多

2、新需求的上线过程是,先上线一台服务器然后观察会不会出问题,如果没有问题则全部上线。

3、分流是关键,但是动态分流是痛点。

三、老分流方案

未分类

方案存在的问题点:

1、nginx.conf配置文件里各种if、set和rewrite,并且容易配置出错。
2、修改完配置文件后,重启或者reload后才能生效。
3、不能实现太复杂的逻辑。
4、不能实现一些特殊分流方式。

四、新分流方案

未分类

功能说明:

  • 采用Redis存放分流策略
  • 分流策略包括按时间来分流,比如每分钟分流多少笔订单,还有按权重分流,比如新老系统之间的比例是1:9
  • 采用OpenResty+lua,整体性能优秀。

单台压测结果:

未分类

配置Lua转发Nginx请求复制

通过配置Nginx来将请求进行复制,转发到其他应用,以下是自己实际搭建的步骤以及自己的理解,方便以后使用

1、环境搭建

实际搭建环境如下:Linux CenterOS 6.5 ,Nginx1.9.0,headers-more-nginx-module-0.31,LuaJIT-2.1.0-beta2,lua-nginx-module-0.10.2,ngx_devel_kit-0.2.19。

以上是搭建成功的各个对应版本,如果版本不对应可能会导致nginx编译失败,github下载后的插件尽量重命名一下,方便使用。

按照参考链接进行编译Nginx。

2、Nginx+Lua文件配置

a、编写一个 copy request 的 lua 脚本copy_req.lua

local res1, res2, action
action = ngx.var.request_method
if action == "POST" then
        arry = {method = ngx.HTTP_POST, body = ngx.req.read_body()}
else
        arry = {method = ngx.HTTP_GET}
end

if ngx.var.svr == "on" then
        res1, res2 = ngx.location.capture_multi {
                { "/product" .. ngx.var.request_uri , arry},
                { "/test" .. ngx.var.request_uri , arry},
        }
else
        res1, res2 = ngx.location.capture_multi {
                { "/product" .. ngx.var.request_uri , arry},
        }
end

if res1.status == ngx.HTTP_OK then
        local header_list = {"Content-Length", "Content-Type", "Content-Encoding", "Accept-Ranges"}
        for _, i in ipairs(header_list) do
                if res1.header[i] then
                        ngx.header[i] = res1.header[i]
                end
        end
        ngx.say(res1.body)  #此处代表只返回生产环境的返回结果
else
        ngx.status = ngx.HTTP_NOT_FOUND
end

此处文件地址引用是可以写觉得地址,相对地址是相对于nginx目录的。
b、配置对应的Nginx配置文件,此处本文地址是conf/vhost/fenliu.conf,在nginx.conf下端加入include vhost/*.conf;

fenliu.conf文件配置如下:

upstream product {
        server  127.0.0.1:80;
}
upstream test {
        server  192.168.1.1:88;
}
server {
        listen      8000;
        #lua_code_cache off;

        location ~* ^/product {
                log_subrequest on;
                rewrite ^/product(.*)$ $1 break;
                proxy_set_header Host $host;
                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_pass http://product;
                access_log logs/product-upstream.log;
        }

        location ~* ^/test {
                log_subrequest on;
                rewrite ^/test(.*)$ $1 break;
                proxy_set_header Host $host;
                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_pass http://test;
                access_log logs/test-upstream.log;
        }

        location ~* ^/(.*)$ {
                client_body_buffer_size 2m;
                set $svr    "on";              #开启或关闭copy功能
                content_by_lua_file conf/vhost/copy_req.lua;
        }
}

此文件很重要,这里备注的是本人自己的理解,^/product,^/test主要就是对这两个路径访问的url进行转发,一个转发到生产,一个到测试,多了一个rewrite是为了重写请求地址,下面会讲到,
^/(.*)$才是重点,是将所有非product,test请求进行请求复制转发。

以上面配置为例,实际使用的流程如下:

1、请求地址:http://ip:8000/hello/req.do
2、nginx不匹配product和test会走最后一个,通过Lua配置会变成两个请求/product/hello/req.do和/test/hello/req.do
3、这时会被nginx的product和test拦截到,进行转发到生产和测试环境,此时地址是不对的,所以使用rewrite进行url重写,
rewrite ^/product(.*)$ $1 break; 匹配/product/hello/req.do会变成/product(/hello/req.do),$1代表/hello/req.do,重写后的地址就会变成我们想要的地址,转发后就变成http://product/hello/req.do。

openresty下lua的function定义及调用

本文主要研究下如何在openresty下lua的function定义及调用。

源码示例

/usr/local/openresty/lualib/resty/string.lua

-- Copyright (C) by Yichun Zhang (agentzh)


local ffi = require "ffi"
local ffi_new = ffi.new
local ffi_str = ffi.string
local C = ffi.C
local setmetatable = setmetatable
local error = error
local tonumber = tonumber


local _M = { _VERSION = '0.09' }


ffi.cdef[[
typedef unsigned char u_char;

u_char * ngx_hex_dump(u_char *dst, const u_char *src, size_t len);

intptr_t ngx_atoi(const unsigned char *line, size_t n);
]]

local str_type = ffi.typeof("uint8_t[?]")


function _M.to_hex(s)
    local len = #s * 2
    local buf = ffi_new(str_type, len)
    C.ngx_hex_dump(buf, s, #s)
    return ffi_str(buf, len)
end


function _M.atoi(s)
    return tonumber(C.ngx_atoi(s, #s))
end


return _M

实例

demo.lua

local _M = { _VERSION = '0.01' }
function _M.hello()
    ngx.say("hello from demo module!")
end
return _M

conf

        location /function {
            content_by_lua '
                local demo = require("demo")
                demo.hello()
            ';
        }

报错

2018/03/26 16:24:15 [error] 5#5: *1 lua entry thread aborted: runtime error: content_by_lua(nginx.conf:69):2: module 'demo' not found:
    no field package.preload['demo']
    no file '/usr/local/openresty/lualib/demo.lua'
    no file '/usr/local/openresty/lualib/demo/init.lua'
    no file './demo.lua'
    no file '/usr/local/openresty/luajit/share/luajit-2.1.0-beta2/demo.lua'
    no file '/usr/local/share/lua/5.1/demo.lua'
    no file '/usr/local/share/lua/5.1/demo/init.lua'
    no file '/usr/local/openresty/luajit/share/lua/5.1/demo.lua'
    no file '/usr/local/openresty/luajit/share/lua/5.1/demo/init.lua'
    no file '/usr/local/openresty/lualib/demo.so'
    no file './demo.so'
    no file '/usr/local/lib/lua/5.1/demo.so'
    no file '/usr/local/openresty/luajit/lib/lua/5.1/demo.so'
    no file '/usr/local/lib/lua/5.1/loadall.so'
stack traceback:
coroutine 0:
    [C]: in function 'require'
    content_by_lua(nginx.conf:69):2: in function <content_by_lua(nginx.conf:69):1>, client: 192.168.99.1, server: , request: "GET /function HTTP/1.1", host: "192.168.99.100:8686"

修复

ADD demo.lua /usr/local/openresty/lualib/demo.lua

小结

从源码可以看出,基本是定义一个_M变量,里头有个_VERSION属性,然后定义_M的function,最后返回_M。另外注意自己定义的类库需要放在openresty查找的路径下面,否则会报错。

nginx安装lua/replace-filter-nginx-module

nginx加载lua和replace-filter-nginx-module模块

一、lua部分

首先下载和安装部分模块

1、下载安装LuaJIT

官网:http://luajit.org/
下载链接:http://luajit.org/download/LuaJIT-2.1.0-beta3.tar.gz

wget http://luajit.org/download/LuaJIT-2.1.0-beta3.tar.gz
tar zxvf LuaJIT-2.1.0-beta3.tar.gz
cd LuaJIT-2.1.0-beta3
make PREFIX=/usr/local/luajit-2.1 
make install PREFIX=/usr/local/luajit-2.1

设置好环境变量

export LUAJIT_LIB=/usr/local/luajit-2.1/lib
export LUAJIT_INC=/usr/local/luajit-2.1/include/luajit-2.1

2、下载ngx_devel_kit

wget https://github.com/simplresty/ngx_devel_kit/archive/v0.3.1rc1.tar.gz
tar zxvf v0.3.1rc1.tar.gz

3、下载lua-nginx-module

wget https://github.com/openresty/lua-nginx-module/archive/v0.10.12rc2.tar.gz
tar zxvf v0.10.12rc2.tar.gz

4、下载安装sregex

wget https://github.com/openresty/sregex/archive/v0.0.1.tar.gz
cd sregex-0.0.1
make
make install

需要做软链

ln -s /usr/local/lib/libsregex.so.0.0.1 /usr/lib64/libsregex.so.0
ln -s /usr/local/lib/libsregex.so.0.0.1 /usr/lib/libsregex.so.0

否则报错:

/usr/local/nginx/sbin/nginx: error while loading shared libraries: libsregex.so.0: cannot open shared object file: No such file or directory

5、下载replace-filter-nginx-module

wget https://github.com/openresty/replace-filter-nginx-module/archive/v0.01rc5.tar.gz
tar zxvf v0.01rc5.tar.gz

6、安装nginx加载以上模块

./configure --user=www --group=www --prefix=/usr/local/nginx --with-http_stub_status_module --with-http_ssl_module  --with-http_realip_module --add-module=../ngx_http_substitutions_filter_module-0.6.4/ --with-http_sub_module --add-module=../ngx_devel_kit-0.3.1rc1 --add-module=../lua-nginx-module-0.10.11 --with-ld-opt=-Wl,-rpath,/usr/local/luajit-2.1/lib --add-module=../replace-filter-nginx-module-0.01rc5/
make
make install

安装完毕

lua 模块管理的一点改进

lua 从 5.2 开始,简化了 5.1 中的模块管理方式,然后一直保持到现在这个样子。

模块用 require 加载,同名模块在一个 vm 中只加载一次,第 2 次开始会返回上次加载的结果。加载模块时会利用 package.path 或 package.cpath 中定义的字符串模板,把模块名转换为文件名,依次尝试打开文件。

我在新项目中,由于整合了不少模块,感觉现有的这套机制有点点不够用。所以我做了一点点小改动,支持了类似 python 的模块管理那样的相对机制。当在一个模块中 require 另一个模块时,会先尝试加载相对路径上的模块,再尝试绝对路径。这样可以方便我们集成独立开始的模块,并放在独立的名字空间中。也方便给模块内置测试子模块。

例如,我独立开发了一个叫 foobar 的模块,它自己有一个子模块叫 foobar.baz ,在集成到系统中时,我希望把它们一起放在 common 名字空间下。使用的时候可以用 require “common.foobar” 来引用。

如果直接用 lua 原生的模块管理机制,我需要修改 foobar 主模块的代码,把里面的 require “foobar.baz” 改成 require “common.foobar.baz” 。同理,如果我不满意 foobar 这个名字,想换名也很麻烦。

所以我希望在新机制下,foobar 的主模块引用自己的子模块 baz,只需要 require “baz” 即可。如果同一目录下有 baz.lua 这个文件,就优先加载它。而外部模块也可以通过 require “foobar.baz” 直接引用这个子模块。

新机制最好可以兼容原生机制,所以不必另起一套模块管理代码。我们要做的只是获取当前模块的名字,在 foobar 主模块中调用 require 时,尝试给参数加上 foobar. 的前缀,引入失败再按原生路径尝试。

好在 lua 原生机制已经把当前模块名作为参数传递进来了。我们只需要实现这么一个函数:

local loaded = package.loaded
local searchpath = package.searchpath

function import(modname)
    if modname then
        local prefix = modname:match "(.*%.).*$" or (modname .. ".")
        return function(name)
            local fullname = prefix .. name
            local m = loaded[fullname] or loaded[name]
            if m then
                return m
            end
            if searchpath(fullname, package.path) then
                return require(fullname)
            else
                return require(name)
            end
        end
    else
        return require
    end
end

这个 import 会生成一个模块导入函数,完成以上逻辑。我把这个函数定义成了全局函数,在项目开头就引入。然后,在每个模块的最前面加入这样一行:

local require = import and import(...) or require

如果定义了 import 则用 import 生成一个 require 代替原生版本;否则直接使用原生版本。

另外,我更倾向于用目录来管理模块。即使一个模块只有一个文件,也放在一个单独的目录中。lua 原生模板的约定是 ?/init.lua 用目录中的 init.lua 作为主模块的入口,我更喜欢 ?/?.lua 直接用目录名同名文件做主入口。

利用redis + lua解决抢红包高并发的问题

抢红包的需求分析

抢红包的场景有点像秒杀,但是要比秒杀简单点。
因为秒杀通常要和库存相关。而抢红包则可以允许有些红包没有被抢到,因为发红包的人不会有损失,没抢完的钱再退回给发红包的人即可。
另外像小米这样的抢购也要比淘宝的要简单,也是因为像小米这样是一个公司的,如果有少量没有抢到,则下次再抢,人工修复下数据是很简单的事。而像淘宝这么多商品,要是每一个都存在着修复数据的风险,那如果出故障了则很麻烦。

淘宝的专家丁奇有个文章有写到淘宝是如何应对秒杀的:《秒杀场景下MySQL的低效–原因和改进》

http://blog.nosqlfan.com/html/4209.html

基于redis的抢红包方案

下面介绍一种基于redis的抢红包方案。

把原始的红包称为大红包,拆分后的红包称为小红包。

1、小红包预先生成,插到数据库里,红包对应的用户ID是null。生成算法见另一篇blog:http://blog.csdn.net/hengyunabc/article/details/19177877

2、每个大红包对应两个redis队列,一个是未消费红包队列,另一个是已消费红包队列。开始时,把未抢的小红包全放到未消费红包队列里。

未消费红包队列里是json字符串,如{userId:’789′, money:’300′}。

3、在redis中用一个map来过滤已抢到红包的用户。

4、抢红包时,先判断用户是否抢过红包,如果没有,则从未消费红包队列中取出一个小红包,再push到另一个已消费队列中,最后把用户ID放入去重的map中。

5、用一个单线程批量把已消费队列里的红包取出来,再批量update红包的用户ID到数据库里。

上面的流程是很清楚的,但是在第4步时,如果是用户快速点了两次,或者开了两个浏览器来抢红包,会不会有可能用户抢到了两个红包?

为了解决这个问题,采用了lua脚本方式,让第4步整个过程是原子性地执行。

下面是在redis上执行的Lua脚本:

-- 函数:尝试获得红包,如果成功,则返回json字符串,如果不成功,则返回空  
-- 参数:红包队列名, 已消费的队列名,去重的Map名,用户ID  
-- 返回值:nil 或者 json字符串,包含用户ID:userId,红包ID:id,红包金额:money  

-- 如果用户已抢过红包,则返回nil  
if redis.call('hexists', KEYS[3], KEYS[4]) ~= 0 then  
  return nil  
else  
  -- 先取出一个小红包  
  local hongBao = redis.call('rpop', KEYS[1]);  
  if hongBao then  
    local x = cjson.decode(hongBao);  
    -- 加入用户ID信息  
    x['userId'] = KEYS[4];  
    local re = cjson.encode(x);  
    -- 把用户ID放到去重的set里  
    redis.call('hset', KEYS[3], KEYS[4], KEYS[4]);  
    -- 把红包放到已消费队列里  
    redis.call('lpush', KEYS[2], re);  
    return re;  
  end  
end  
return nil  

下面是测试代码:

public class TestEval {  
    static String host = "localhost";  
    static int honBaoCount = 1_0_0000;  

    static int threadCount = 20;  

    static String hongBaoList = "hongBaoList";  
    static String hongBaoConsumedList = "hongBaoConsumedList";  
    static String hongBaoConsumedMap = "hongBaoConsumedMap";  

    static Random random = new Random();  

//  -- 函数:尝试获得红包,如果成功,则返回json字符串,如果不成功,则返回空  
//  -- 参数:红包队列名, 已消费的队列名,去重的Map名,用户ID  
//  -- 返回值:nil 或者 json字符串,包含用户ID:userId,红包ID:id,红包金额:money  
    static String tryGetHongBaoScript =   
//          "local bConsumed = redis.call('hexists', KEYS[3], KEYS[4]);n"  
//          + "print('bConsumed:' ,bConsumed);n"  
            "if redis.call('hexists', KEYS[3], KEYS[4]) ~= 0 thenn"  
            + "return niln"  
            + "elsen"  
            + "local hongBao = redis.call('rpop', KEYS[1]);n"  
//          + "print('hongBao:', hongBao);n"  
            + "if hongBao thenn"  
            + "local x = cjson.decode(hongBao);n"  
            + "x['userId'] = KEYS[4];n"  
            + "local re = cjson.encode(x);n"  
            + "redis.call('hset', KEYS[3], KEYS[4], KEYS[4]);n"  
            + "redis.call('lpush', KEYS[2], re);n"  
            + "return re;n"  
            + "endn"  
            + "endn"  
            + "return nil";  
    static StopWatch watch = new StopWatch();  

    public static void main(String[] args) throws InterruptedException {  
//      testEval();  
        generateTestData();  
        testTryGetHongBao();  
    }  

    static public void generateTestData() throws InterruptedException {  
        Jedis jedis = new Jedis(host);  
        jedis.flushAll();  
        final CountDownLatch latch = new CountDownLatch(threadCount);  
        for(int i = 0; i < threadCount; ++i) {  
            final int temp = i;  
            Thread thread = new Thread() {  
                public void run() {  
                    Jedis jedis = new Jedis(host);  
                    int per = honBaoCount/threadCount;  
                    JSONObject object = new JSONObject();  
                    for(int j = temp * per; j < (temp+1) * per; j++) {  
                        object.put("id", j);  
                        object.put("money", j);  
                        jedis.lpush(hongBaoList, object.toJSONString());  
                    }  
                    latch.countDown();  
                }  
            };  
            thread.start();  
        }  
        latch.await();  
    }  

    static public void testTryGetHongBao() throws InterruptedException {  
        final CountDownLatch latch = new CountDownLatch(threadCount);  
        System.err.println("start:" + System.currentTimeMillis()/1000);  
        watch.start();  
        for(int i = 0; i < threadCount; ++i) {  
            final int temp = i;  
            Thread thread = new Thread() {  
                public void run() {  
                    Jedis jedis = new Jedis(host);  
                    String sha = jedis.scriptLoad(tryGetHongBaoScript);  
                    int j = honBaoCount/threadCount * temp;  
                    while(true) {  
                        Object object = jedis.eval(tryGetHongBaoScript, 4, hongBaoList, hongBaoConsumedList, hongBaoConsumedMap, "" + j);  
                        j++;  
                        if (object != null) {  
//                          System.out.println("get hongBao:" + object);  
                        }else {  
                            //已经取完了  
                            if(jedis.llen(hongBaoList) == 0)  
                                break;  
                        }  
                    }  
                    latch.countDown();  
                }  
            };  
            thread.start();  
        }  

        latch.await();  
        watch.stop();  

        System.err.println("time:" + watch.getTotalTimeSeconds());  
        System.err.println("speed:" + honBaoCount/watch.getTotalTimeSeconds());  
        System.err.println("end:" + System.currentTimeMillis()/1000);  
    }  
}

测试结果20个线程,每秒可以抢2.5万个,足以应付绝大部分的抢红包场景。

如果是真的应付不了,拆分到几个redis集群里,或者改为批量抢红包,也足够应付。

总结

redis的抢红包方案,虽然在极端情况下(即redis挂掉)会丢失一秒的数据,但是却是一个扩展性很强,足以应付高并发的抢红包方案。