GVKun编程网logo

总结Laravel项目部署到线上需要注意的一些问题(laravel项目部署到服务器)

14

在本文中,我们将带你了解总结Laravel项目部署到线上需要注意的一些问题在这篇文章中,我们将为您详细介绍总结Laravel项目部署到线上需要注意的一些问题的方方面面,并解答laravel项目部署到服

在本文中,我们将带你了解总结Laravel项目部署到线上需要注意的一些问题在这篇文章中,我们将为您详细介绍总结Laravel项目部署到线上需要注意的一些问题的方方面面,并解答laravel项目部署到服务器常见的疑惑,同时我们还将给您一些技巧,以帮助您实现更有效的ANDROID动态加载 使用SO库时要注意的一些问题、C/C++ 语言要注意的一些问题、Java从单体架构升级到微服务要注意的一些问题、laravel 项目部署到生产环境

本文目录一览:

总结Laravel项目部署到线上需要注意的一些问题(laravel项目部署到服务器)

总结Laravel项目部署到线上需要注意的一些问题(laravel项目部署到服务器)

下面由Laravel框架教程栏目给大家总结Laravel项目部署到线上需要注意的一些问题,希望对需要的朋友有所帮助!

准备部署 Laravel 应用到生产环境时,却出现了以下一些问题,你在本地上基本不会出现问题,但是到线上很多问题都出来了。整理了一些问题与bug,希望在你部署laravel项目的时候,如果出现类似问题,可以用得到吧! 部署不出现任何问题,那就再再好不过了。

首先,我们再做调试的时候,请先开启PHP显示错误,以便做调试

vim /usr/local/PHP/etc/PHP.ini
修改display_errors = Off
改为display_errors = On

改完后记得要重启服务器。 

1 目录权限问题

为了运行 Laravel,我们需要为一些项目目录配置权限.

Laravel 项目需要对目录 storage/, bootstrap/cache, public / 赋予读写权限

//赋予三个目录读写权限
chmod -R 777 bootstrap/
chmod -R 777 storage/
chmod -R 777 public/

如果你用的是一键安装包lnmp,请注意,LNMP 一键安装包中含有.user.ini,权限会被拒绝。

需使用:

chattr -i /{目录}/.user.ini

并删除:

rm .user.ini

2 Nginx的配置文件的问题

假设你的Nginx.conf文件的路径是放在这里:/usr/local/Nginx/conf/Nginx.conf文件,找到 server{}字段中

如下代码

#include enable-PHP.conf;

你的Nginx里存不存在这个文件,请注释,因为这个会导致500错误。原因是:

引入了 PHP 配置,其中有句 try_files 开启就有报错.

#新增 支持laravel 优雅链接,在laravel 文档里有说明
location / {
    try_files $uri $uri/ /index.PHP?$query_string;
}

#新增 支持PHP 的配置
location ~ \.PHP$ {

#不能有下面这句 try_files ,不然报错500
# try_files $uri /index.PHP =404;

fastcgi_split_path_info ^(.+\.PHP)(/.+)$;

#这句注意 后面是.sock 不是127.0.0..1

fastcgi_pass  unix:/tmp/php-cgi.sock;
fastcgi_index index.PHP;

include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}

附件:给一个laravel的Nginx配置

server{
    listen 80;
    server_name 网站域名;
    index index.PHP index.html index.htm default.html default.htm default.PHP;
    root  /var/www/html/act/public;   //网站存放目录,laravel的入口文件在public里

    #include rewrite/none.conf;
    #error_page   404   /404.html;

    # Deny access to PHP files in specific directory
    #location ~ /(wp-content|uploads|wp-includes|images)/.*\.PHP$ { deny all; }

    #include enable-PHP-pathinfo.conf;
    #添加以下这句就好了
    location / {
       try_files $uri $uri/ /index.PHP?$query_string;
    }

    error_page 404 /404.html;
        location = /40x.html {
    }

    error_page 500 502 503 504 /50x.html;
        location = /50x.html {
    }

    location ~ \.PHP$ {
         root /var/www/html/act/public;
         fastcgi_pass 127.0.0.1:9000;
         fastcgi_index index.PHP;
         fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
         include fastcgi_params;
    }#    if (!-e $request_filename){#         rewrite ^/(mo_bile|admin|physician|home|seller)/(.*)$ /$1/index.PHP?$2;#    }

    location ~ \.PHP$ {
          fastcgi_param PATH_INFO $request_uri;
    }


    access_log  /home/wwwlogs/hd.log;}

3 PHP扩展要记得开启

部署项目之前要先确保PHP.ini里的扩展已经开启,开启的扩展有:PHP_fileinfo, PHP_mbstring, PHP_openssl,这几个都是laravel需要的。

不管是修改了Nginx还是PHP.ini,修改完后,请记得要重启Nginx与PHP-fpm。

4 laravel项目在git上clone到线上可能会缺少一下核心库,开启PHP错误显示会看到类似以下的问题

Warning: require(): open_basedir restriction in effect. 
File(/home/wwwroot/***/bootstrap/autoload.PHP) is not within the allowed path(s): 
(/home/wwwroot/***/public/:/tmp/:/proc/) in /home/wwwroot/***/public/index.PHP on line 22


Warning: require(/home/wwwroot/***/bootstrap/autoload.PHP): Failed to open stream: Operation 
not permitted in /home/wwwroot/***/public/index.PHP on line 22


Fatal error: require(): Failed opening required 
'/home/wwwroot/***/public/../bootstrap/autoload.PHP' 
(include_path='.:/usr/local/PHP/lib/PHP') in /home/wwwroot/***/public/index.PHP on line 22

此时你需要composer 更新第三方 vendor 组件
在项目目录下执行composer update,请自行更新composer到最新版本。

如果在更新中出错,请网上查找相应的composer错误,这个很好解决的。 

5 laravel从git上clone到线上目录出现app_key的错误的话的,请在.env文件里加app_key。

//生成key,在项目根目录下执行命令来获取laravel项目app_key
PHP artisan key:generate

//或者可以修改配置文件.env中的APP_KEY参数APP_KEY=base64:akjIOLlieujKw0yEUbwjJdP5lPWHkk3uw39CnAhfdasfsaddfggghssda+

6 laravel上传到线上出现The cipher and / or key length are invalid 的

这个问题很多都是读取.env的时候为null造成的。

首先你应该检查config的app.PHP里是否有存在key与cipher的配置

'key'             => env('APP_KEY'),'
cipher'          => 'AES-256-CBC',

有存在也要查找.env里是否有app_key。有存在的话,请操作:

PHP artisan config:cache

因为是env失效,所以接下来你要做的是清除缓存,重新来过,重要的一步就是要重新启动NginxPHP-fpm

7 Laravel 中 seeder 执行失败

  • 当第一次执行完 PHP artisan db:seed 后,增加新的 seeder 文件时执行会报错。错误信息如下 [ReflectionException] Class ***TableSeeder does not exist

  • 确保新的 seeder 文件和全局 database seeder 是在同一个 seeder 目录下了,仍然会出现这个问题的原因是: 我们需要清理下之前执行生成的 classmap 信息。

  • 在控制台中执行 composer dump-autoload,然后再执行 PHP artisan db:seed

部署到线上的经常会出现的,我遇到的就这么些问题,也许你会遇到更多的问题,或许你不会遇到问题。或许上面我遇到的问题能给予你一些帮助吧!

ANDROID动态加载 使用SO库时要注意的一些问题

ANDROID动态加载 使用SO库时要注意的一些问题

基本信息

  • 项目:android-dynamical-loading

Android项目里的SO库

正好动态加载系列文章谈到了加载SO库的地方,我觉得这里可以顺便谈谈使用SO库时需要注意的一些问题。或许这些问题对于经常和SO库开发打交道的同学来说已经是老生长谈,但是既然要讨论一整个动态加载系列,我想还是有必要说说使用SO库时的一些问题。

在项目里使用SO库非常简单,在 加载SD卡中的SO库 中也有谈到,只需要把需要用到的SO库拷贝进 jniLibs(或者Eclipse项目里面的libs) 中,然后在JAVA代码中调用 System.loadLibrary("xxx") 加载对应的SO库,就可以使用JNI语句调用SO库里面的Native方法了。

但是有同学注意到了,SO库文件可以随便改文件名,却不能任意修改文件夹路径,而是“armeabi”、“armeabi-v7a”、“x86”等文件夹名有着严格的要求,这些文件夹名有什么意义么?

SO库类型和CPU架构类型

原因很简单,不同CPU架构的设备需要用不同类型SO库(从文件名也可以猜出来个大概嘛 ╮( ̄▽ ̄")╭)。

记得还在学校的时候,提及ARM处理器时,老师说以后移动设备的CPU基本就是ARM类型的了。老师不曾欺我,早期的Android系统几乎只支持ARM的CPU架构,不过现在至少支持以下七种不同的CPU架构:ARMv5,ARMv7,x86,MIPS,ARMv8,MIPS64和x86_64。每一种CPU类型都对应一种ABI(Application Binary Interface),“armeabi-v7a”文件夹前面的“armeabi”指的就是ARM这种类型的ABI,后面的“v7a”指的是ARMv7。这7种CPU类型对应的SO库的文件夹名是:armeabi,armeabi-v7a,x86,mips,arm64-v8a,mips64,x86_64。

不同类型的移动设备在运行APP时,需要加载自己支持的类型的SO库,不然就GG了。通过 Build.SUPPORTED_ABIS 我们可以判断当前设备支持的ABI,不过一般情况下,不需要开发者自己去判断ABI,Android系统在安装APK的时候,不会安装APK里面全部的SO库文件,而是会根据当前CPU类型支持的ABI,从APK里面拷贝最合适的SO库,并保存在APP的内部存储路径的 libs 下面。(这里说一般情况,是因为有例外的情况存在,比如我们动态加载外部的SO库的时候,就需要自己判断ABI类型了。)

一种CPU架构 = 一种对应的ABI参数 =  一种对应类型的SO库

到这里,我们发现使用SO库的逻辑还是比较简单的,但是Android系统加载SO库的逻辑还是给我们留下了一些坑。

使用SO库时要注意的一些问题

1. 别把SO库放错地方

SO库其实都是APP运行时加载的,也就是说APP只有在运行的时候才知道SO库文件的存在,这就无法通过静态代码检查或者在编译APP时检查SO库文件是否正常。所以,Android开发对SO库的存放路径有严格的要求。

使用SO库的时候,除了“armeabi-v7a”等文件夹名需要严格按照规定的来自外,SO库要放在项目的哪个文件夹下也要按照套路来,以下是一些总结:

  • Android Studio 工程放在 jniLibs/xxxabi 目录中(当然也可以通过在build.gradle文件中的设置jniLibs.srcDir属性自己指定);

  • Eclipse 工程放在 libs/xxxabi 目录中(这也是使用ndk-build命令生成SO库的默认目录);

  • aar 依赖包中位于 jni/ABI 目录中(SO库会自动包含到引用AAR压缩包到APK中);

  • 最终构建出来的APK文件中,SO库存在 lib/xxxabi 目录中(也就是说无论你用什么方式构建,只要保证APK包里SO库的这个路径没错就没问题);

  • 通过 PackageManager 安装后,在小于 Android 5.0 的系统中,SO库位于 APP 的 nativeLibraryPath 目录中;在大于等于 Android 5.0 的系统中,SO库位于 APP 的 nativeLibraryRootDir/CPU_ARCH 目录中;

既然扯到了这里,顺便说一下,我在使用 Android Studio 1.5 构建APK的时候,发现 Gradle 插件只会默认打包application类型的module的jniLibs下面的SO库文件,而不会打包aar依赖包的SO库,所以会导致最终构建出来的APK里的SO库文件缺失。暂时的解决方案是把所有的SO库都放在application模块中(这显然不是很好的解决方案),不知道这是不是Studio的BUG,同事的解决方案是通过修改Gradle插件来增加对aar依赖包的SO库的打包支持(GitHub有开源的第三方Gradle插件项目,使用Java和Groovy语言开发)。

2. 尽可能提供CPU支持的最优SO库

当一个应用安装在设备上,只有该设备支持的CPU架构对应的SO库会被安装。但是,有时候,设备支持的SO库类型不止一种,比如大多的X86设备除了支持X86类型的SO库,还兼容ARM类型的SO库(目前应用市场上大部分的APP只适配了ARM类型的SO库,X86类型的设备如果不能兼容ARM类型的SO库的话,大概要嗝屁了吧)。

所以如果你的APK只适配了ARM类型的SO库的话,还是能以兼容的模式在X86类型的设备上运行(比如华硕的平板),但是这不意味着你就不用适配X86类型的SO库了,因为X86的CPU使用兼容模式运行ARM类型的SO库会异常卡顿(试着回想几年前你开始学习Android开发的时候,在PC上使用AVD模拟器的那种感觉)。

3. 注意SO库的编译版本

除了要注意使用了正确CPU类型的SO库,也要注意SO库的编译版本的问题。虽然现在的Android Studio支持在项目中直接编译SO库,但是更多的时候我们还是选择使用事先编译好的SO库,这时就要注意了,编译APK的时候,我们总是希望使用最新版本的build-tools来编译,因为Android SDK最新版本会帮我们做出最优的向下兼容工作。

但是这对于编译SO库来说就不一样了,因为NDK平台不是向下兼容的,而是向上兼容的。应该使用app的minSdkVersion对应的版本的NDK标本来编译SO库文件,如果使用了太高版本的NDK,可能会导致APP性能低下,或者引发一些SO库相关的运行时异常,比如“UnsatisfiedLinkError”,“dlopen: failed”以及其他类型的Crash。

一般情况下,我们都是使用编译好的SO库文件,所以当你引入一个预编译好的SO库时,你需要检查它被编译所用的平台版本。

4. 尽可能为每种CPU类型都提供对应的SO库

比如有时候,因为业务的需求,我们的APP不需要支持AMR64的设备,但这不意味着我们就不用编译ARM64对应的SO库。举个例子,我们的APP只支持armeabi-v7a和x86架构,然后我们的APP使用了一个第三方的Library,而这个Library提供了AMR64等更多类型CPU架构的支持,构建APK的时候,这些ARM64的SO库依然会被打包进APK里面,也就是说我们自己的SO库没有对应的ARM64的SO库,而第三方的Library却有。这时候,某些ARM64的设备安装该APK的时候,发现我们的APK里带有ARM64的SO库,会误以为我们的APP已经做好了AMR64的适配工作,所以只会选择安装APK里面ARM64类型的SO库,这样会导致我们自己项目的SO库没有被正确安装(虽然armeabi-v7a和x86类型的SO库确实存在APK包里面)。

这时正确的做法是,给我们自己的SO库也提供AMR64支持,或者不打包第三方Library项目的ARM64的SO库。使用第二种方案时,可以把APK里面不需要支持的ABI文件夹给删除,然后重新打包,而在Android Studio下,则可以通过以下的构建方式指定需要类型的SO库。

productFlavors {
    flavor1 {
        ndk {
            abiFilters "armeabi-v7a"
            abiFilters "x86"
            abiFilters "armeabi"
        }
    }
    flavor2 {
        ndk {
            abiFilters "armeabi-v7a"
            abiFilters "x86"
            abiFilters "armeabi"
            abiFilters "arm64-v8a"
            abiFilters "x86_64"
        }
    }
}

需要说明的是,如果我们的项目是SDK项目,我们最好提供全平台类型的SO库支持,因为APP能支持的设备CPU类型的数量,就是项目中所有SO库支持的最少CPU类型的数量(使用我们SDK的APP能支持的CPU类型只能少于等于我们SDK支持的类型)。

5. 不要通过“减少其他CPU类型支持的SO库”来减少APK的体积

确实,所有的x86/x86_64/armeabi-v7a/arm64-v8a设备都支持armeabi架构的SO库,因此似乎移除其他ABIs的SO库是一个减少APK大小的好办法。但事实上并不是,这不只影响到函数库的性能和兼容性。

X86设备能够很好的运行ARM类型函数库,但并不保证100%不发生crash,特别是对旧设备,兼容只是一种保底方案。64位设备(arm64-v8a, x86_64, mips64)能够运行32位的函数库,但是以32位模式运行,在64位平台上运行32位版本的ART和Android组件,将丢失专为64位优化过的性能(ART,webview,media等等)。

过减少其他CPU类型支持的SO库来减少APK的体积不是很明智的做法,如果真的需要通过减少SO库来做APK瘦身,我们也有其他办法。

减少SO库体积的正确姿势

1. 构建特定ABI支持的APK

我们可以构建一个APK,它支持所有的CPU类型。但是反过来,我们可以为每个CPU类型都单独构建一个APK,然后不同CPU类型的设备安装对应的APK即可,当然前提是应用市场得提供用户设备CPU类型设别的支持,就目前来说,至少PLAY市场是支持的。

Gradle可以通过以下配置生成不同ABI支持的APK(引用自别的文章,没实际使用过):

android {
    ...
    splits {
        abi {
            enable true
            reset()
            include ''x86'', ''x86_64'', ''armeabi-v7a'', ''arm64-v8a'' //select ABIs to build APKs for
            universalApk true //generate an additional APK that contains all the ABIs
        }
    }

    // map for the version code
    project.ext.versionCodes = [''armeabi'': 1, ''armeabi-v7a'': 2, ''arm64-v8a'': 3, ''mips'': 5, ''mips64'': 6, ''x86'': 8, ''x86_64'': 9]

    android.applicationVariants.all { variant ->
        // assign different version code for each output
        variant.outputs.each { output ->
            output.versionCodeOverride =
                    project.ext.versionCodes.get(output.getFilter(com.android.build.OutputFile.ABI), 0) * 1000000 + android.defaultConfig.versionCode
        }
    }
 }

2. 从网络下载当前设备支持的SO库

说到这里,总算回到动态加载的主题了。⊙﹏⊙

使用Android的动态加载技术,可以加载外部的SO库,所以我们可以从网络下载SO库文件并加载了。我们可以下载所有类型的SO库文件,然后加载对应类型的SO库,也可以下载对应类型的SO库然后加载,不过无论哪种方式,我们最好都在加载SO库前,对SO库文件的类型做一下判断。

我个人的方案是,存储在服务器的SO库依然按照APK包的压缩方式打包,也就是,SO库存放在APK包的 libs/xxxabi 路径下面,下载完带有SO库的APK包后,我们可以遍历libs路径下的所有SO库,选择加载对应类型的SO库。

具体实现代码看上去像是:

/**
 * 将一个SO库复制到指定路径,会先检查改SO库是否与当前CPU兼容
 *
 * @param sourceDir     SO库所在目录
 * @param so            SO库名字
 * @param destDir       目标根目录
 * @param nativeLibName 目标SO库目录名
 * @return
 */
public static boolean copySoLib(File sourceDir, String so, String destDir, String nativeLibName) throws IOException {

    boolean isSuccess = false;
    try {
        LogUtil.d(TAG, "[copySo] 开始处理so文件");

        if (Build.VERSION.SDK_INT >= 21) {
            String[] abis = Build.SUPPORTED_ABIS;
            if (abis != null) {
                for (String abi : abis) {
                    LogUtil.d(TAG, "[copySo] try supported abi:" + abi);
                    String name = "lib" + File.separator + abi + File.separator + so;
                    File sourceFile = new File(sourceDir, name);
                    if (sourceFile.exists()) {
                        LogUtil.i(TAG, "[copySo] copy so: " + sourceFile.getAbsolutePath());
                        isSuccess = FileUtil.copyFile(sourceFile.getAbsolutePath(), destDir + File.separator + nativeLibName + File.separator + so);
                        //api21 64位系统的目录可能有些不同
                        //copyFile(sourceFile.getAbsolutePath(), destDir + File.separator +  name);
                        break;
                    }
                }
            } else {
                LogUtil.e(TAG, "[copySo] get abis == null");
            }
        } else {
            LogUtil.d(TAG, "[copySo] supported api:" + Build.CPU_ABI + " " + Build.CPU_ABI2);

            String name = "lib" + File.separator + Build.CPU_ABI + File.separator + so;
            File sourceFile = new File(sourceDir, name);

            if (!sourceFile.exists() && Build.CPU_ABI2 != null) {
                name = "lib" + File.separator + Build.CPU_ABI2 + File.separator + so;
                sourceFile = new File(sourceDir, name);

                if (!sourceFile.exists()) {
                    name = "lib" + File.separator + "armeabi" + File.separator + so;
                    sourceFile = new File(sourceDir, name);
                }
            }
            if (sourceFile.exists()) {
                LogUtil.i(TAG, "[copySo] copy so: " + sourceFile.getAbsolutePath());
                isSuccess = FileUtil.copyFile(sourceFile.getAbsolutePath(), destDir + File.separator + nativeLibName + File.separator + so);
            }
        }

        if (!isSuccess) {
            LogUtil.e(TAG, "[copySo] 安装 " + so + " 失败 : NO_MATCHING_ABIS");
            throw new IOException("install " + so + " fail : NO_MATCHING_ABIS");
        }

    } catch (IOException e) {
        e.printStackTrace();
        throw e;
    }

    return true;
}

总结

  1. 一种CPU架构 = 一种ABI = 一种对应的SO库;

  2. 加载SO库时,需要加载对应类型的SO库;

  3. 尽量提供全平台CPU类型的SO库支持;

题外话,SO库的使用本身就是一种最纯粹的动态加载技术,SO库本身不参与APK的编译过程,使用JNI调用SO库里的Native方法的方式看上去也像是一种“硬编程”,Native方法看上去与一般的Java静态方法没什么区别,但是它的具体实现却是可以随时动态更换的(更换SO库就好),这也可以用来实现热修复的方案,与Java方法一旦加载进内存就无法再次更换不同,Native方法不需要重启APP就可以随意更换。

出于安全和生态控制的原因,Google Play市场不允许APP有加载外部可执行文件的行为,一旦你的APK里被检查出有额外的可执行文件时就不好玩了,所以现在许多APP都偷偷把用于动态加载的可执行文件的后缀名换成“.so”,这样被发现的几率就降低了,因为加载SO库看上去就是官方合法版本的动态加载啊(不然SO库怎么工作),虽然这么做看起来有点掩耳盗铃。

参考文章

  • 关于Android的.so文件你所需要知道的

  • Android-Plugin-Framework

C/C++ 语言要注意的一些问题

C/C++ 语言要注意的一些问题

1,关键字volatile

<!-- lang: cpp -->
   long square(volatile int x) 
   {   //int a=x;
    return a*a;
    }    在此输入代码

经过编译器优化后可能代码变为

<!-- lang: cpp -->
 long square(volatile int x) 
 {   int a=x;
     int b=x;
    return a*a;
  }

因为变量x可能被意想不到的改变,所以不一定得到x的平方值。 应该把代码变为

<!-- lang: cpp -->
 long square(volatile int x) 
 {   int a=x;
    return a*a;
  }

2,malloc(0)的返回值 malloc(0)返回堆上的任意一个字节的地址,并且返回的地址空间可以对其进行操作。


3,typedef和#define 定义数据类型的别名,哪个最好 最好用typedef,原因如下:

<!-- lang: cpp -->
typedef struct str * tpstr;
#define struct str * dpstr

当定义变量时,tpstr s1,s2;定义的都是指针变量。 而 dpstr s1,s2;相当于struct str * s1,s2; s1是指针变量,s2是结构变量,不是自己最初想要的。


4,无符号数和有符号运算,结果为无符号,无符号和有符号数最好不要混用!内存中的编码为补码!正数为它本身,负数为补码(反码加1).


5,64 位机上的 64 位类型是什么样的?

C99 标准定义了 long long 类型, 其长度可以保证至少 64 位, 这种类型在某些 编译器上实现已经颇有时日了。其它的编译器则实现了类似 longlong 的扩展。


6,怎样定义和声明函数和全局变量? 首先要理解定义和声明的概念,两者的区别是定义要分配存储空间,声明告诉编译器要使用 某个变量类型和名字。同一个变量只能定义一次(可以指定初值),但可以声明多次。 例如,int a ,声明的时候就建立了存储空间。这是定义性声明,就是定义。 而 extern int a ,只是声明变量a在别的文件中定义了,是一种引用声明,所以说声明包含着定义,定义是声明的特例。 函数的声明和定义比较简单,不加“{}”就是声明,加“{}”就是定义。 而通常函数、变量的声明和定义都是分开在不同文件,声明放在.h头文件中,而定义是包含头文件放在.c中。


7,C 如何模拟继承等面向对象的程序语言的方法? 由于可以定义函数指针,因此定义的结构体直接加入函数指针就可以实现简单的方法,结构体进行简单的嵌套,就可以实现简单的继承!


8,这样的代码能不能被执行:a[i]=i++; 不能执行,原因i++表达式有一个副作用,改变i的值,由于i在同一表达式被其他地方引用,导致无定义的结果,无法判断该值是旧值还是新值。


9,能否用括号来强制改变函数的调用顺序 例如 f1()+f2()*f3();尽管我们知道运算规则是先算乘法再算加法,但我们并不知道三个函数的调用顺序,如果想获得我们期望的结果,只有使用独立语句和明确的临时变量。


10,两个整数相除结果为整数,整数才可求余,余数的符号与左边数的符号相同。


11,C++中的内联函数,定义方法是:在函数定义时,在函数的类型前增加修饰词inline。 其实质
是用存储空间(把代码插入到函数调用处)来减少执行时间。 使用内联函数要注意的问题:


一、C++中,除在函数体内含有循环,switch分支和复杂嵌套的if语句外,所有的函数均可定义为内联函数。


二、内联函数也要定义在前,调用在后。形参与实参之间的关系与一般的函数相同。


三、对于用户指定的内联函数,编译器是否作为内联函数来处理由编译器自行决定。说明内联函数时,只是请求编译器当出现这种函数调用时,作为内联函数的扩展来实现,而不是命令编译器要这样去做。


四、正如前面所述,内联函数的实质是采用空间换取时间,即可加速程序的执行,当出现多次调用同一内联函数时,程序本身占用的空间将有所增加。如上例中,内联函数仅调用一次时,并不增加程序占用的存储间。


12,关于C++的参数


一、参数可以有缺省值,但不可以靠左边缺省。

<!-- lang: cpp -->
void f(int a,int b=20);

二、函数原型说明时参数可以没有变量名。

<!-- lang: cpp -->
void f(int,long b,int c=20);

三、只能在前面定义一次缺省值,即原型说明时定义了缺省值,后面函数的定义不可有缺省值。


四、关于可变参数,前面的介绍都是参数数目固定的参数,但是在某些时候,函数参数个数并不能确定,而在调用的时候才确定,针对这种情况,C++定义允许参数个数不固定的函数。


首先,要用到va_start()、va_arg()、 va_end()这三个库函数,必须包含头文件“stdarg.h”,。


其次,要说明一个va_list类型的变量,va_list与int,float类同,它是C++系统预定义的一个数据类型(非float),只有通过这种类型的变量才能从实际参数表中取出可变有参数。如定义下面函数:

<!-- lang: cpp -->
  void fun(int a,int b,...){ //其中"..."表示可变参数,而可变参数前面是确定的参数b
  va_list   ap;
  va_start(ap,b);           //初始化,从确定参数b开始取可变参数
  int temp=va_arg(ap,int);          //依次取参数,int为可变参数的数据类型名
  va_end(ap);             //完成收尾工作
 }

使用参数数目可变的函数时要注意以下几点:


一、在定义函数时,固定参数部分必须放在参数表的前面,可变参数在后面,并用省略  号“...”表示可变参数。在函数调用时,可以没有可变的参数。

二、必须使用函数va_start()来初始化可变参数,为取第一个可变的参数作好准备工作;使用函数va_arg()依次取各个可变的参数值;最后用函数va_end()做好结束工作,以便能正确地返回。

三、在调用参数个数可变的函数时,必定有一个参数指明可变参数的个数或总的实参个数。

13,C++运算符的重载,当用成员函数实现运算符的重载时,运算符重载函数的参数只能有二种情况:没有参数或带有一个参数。对于只有一个操作数的运算符(如++),在重载这种运算符时,通常不能有参数;而对于有二个操作数的运算符,只能带有一个参数。这参数可以是对象,对象的引用,或其它类型的参数。在C++中不允许重载有三个操作数的运算符。

Java从单体架构升级到微服务要注意的一些问题

Java从单体架构升级到微服务要注意的一些问题

这篇文章主要介绍了Java从单体架构升级到微服务要注意的一些问题,对架构感兴趣的同学,可以参考下

目录

前言

划清业务边界

代码层次结构

避免多边界业务的关联查询

Controller层尽量不做业务逻辑处理

后记

前言

由于近年来的移动端的发展和 2C模式 的红利,一些在风口的企业的业务得到爆发式增长。从架构层面来说,业务驱动技术的变革,所以微服务架构的概念得到很多企业的青睐,因为可以解决服务的大流量和高并发以及稳定性的要求。

但是任何架构设计不是一蹴而就的,不能从起步就开始使用微服务,一般都是先通过单体架构来快速实现需求和抢占市场,然后再迭代式扩展。不能一口气吃个胖子。

这几年自己有经历从单体到微服务的架构演变,也有直接参与到已经落地的微服务架构的项目中。见过好的架构设计,也见过一些孬的设计。好的架构设计,代码结构优雅,分层清晰,业务边界划分明朗,业务开发人员职责清晰。不好的设计就会导致代码混乱难以维护,对新需求无法快速应变,开发人员容易在补丁上打补丁,最后造成积重难返不得不重构。

架构师需要从业务层面和未来业务发展有个全面的规划,让架构高可用,易扩展,灵活易使用,隐藏其复杂性。好的架构会让下面的业务开发人员按照既定的模式“傻瓜式”编程。

既然第一步是单体架构,那么好的单体架构设计,为我们后期的微服务拆分会有事半功倍的效果。避免重复劳动和过多的重写。我们可以从这些方面进行一些有效的设计。

划清业务边界

如果对未来的架构有微服务的考虑,那么在单体架构的时候就需要理清业务边界的问题,常见的简单划分就是以业务区分,例如:用户,商品,订单,支付,权限等等,具体的拆分程度可根据自身业务量和需要做划分。

当前流行的 DDD(领域驱动设计)可以作为一个指导原则,但是 DDD 比较偏向于理论,需要执行人员有良好的专业能力才能实施的比较好。

代码层次结构

业务区分好之后,就是项目代码模块的设计。在代码层我们需要根据MVC的模式,建议的代码设计层次如下:

├─demo-common │ │ demo-common.iml │ │ pom.xml │ │ │ └─src │ ├─main │ │ ├─java │ │ └─resources │ └─test │ └─java ├─demo-dao │ │ demo-dao.iml │ │ pom.xml │ │ │ └─src │ ├─main │ │ ├─java │ │ └─resources │ └─test │ └─java ├─demo-service │ │ demo-service.iml │ │ pom.xml │ │ │ └─src │ ├─main │ │ ├─java │ │ └─resources │ └─test │ └─java └─demo-web │ demo-web.iml │ pom.xml │ └─src ├─main │ ├─java │ └─resources └─test └─java

主要包含4个 module 模块

demo-common:基础模块,枚举,常亮类,工具类,配置类。

demo-dao:Dao层,mapper接口,mapper.xml。

demo-service:服务接口提供层,业务service接口。

demo-web:web层,Controller类,服务接口,与外部进行交互。

各模块之间的依赖关系为:

项目 Module 模块设计完成之后,每个模块的内部 package 包如何设计呢?通常有两种划分模式:根据业务模块然后内部按MVC划分,根据MVC模式然后内部按业务划分。

1、根据业务模块划分,就是将每个业务模块作为一个 package,然后每个package里面有自己的 MVC,这样就做到业务模块的隔离。

2、根据 MVC 模式划分,先根据 MVC模式划分不同的包,service,serviceImpl,dto等,然后再是各个业务自己的模型和服务接口。

针对上述的两个划分模式,个人的选择是根据业务模式划分,这样的包设计与后期微服务拆分有良好的匹配度,拆分的时候只需要将每个业务包下的代码 copy 到新的微服务中就行了,易迁移变动小。每个模块中对不同的业务通过 package 包名进行划分,例如:com.example.jajian.service.order、com.example.jajian.service.user等。

└─src ├─main │ ├─java │ │ └─com │ │ └─example │ │ └─jajian │ │ ├─common │ │ │ BaserService.java │ │ │ │ │ └─service │ │ ├─order │ │ │ ├─dto │ │ │ │ OrderDto.java │ │ │ │ │ │ │ └─service │ │ │ │ OrderService.java │ │ │ │ │ │ │ └─impl │ │ │ OrderServiceImpl.java │ │ │ │ │ ├─pay │ │ │ ├─dto │ │ │ │ PayDto.java │ │ │ │ │ │ │ └─service │ │ │ │ PayService.java │ │ │ │ │ │ │ └─impl │ │ │ PayServiceImpl.java │ │ │ │ │ └─user │ │ ├─dto │ │ │ UserDto.java │ │ │ │ │ └─service │ │ │ UserService.java │ │ │ │ │ └─impl │ │ UserServiceImpl.java │ │ │ └─resources └─test └─java

这样划分有什么好处?我们单体架构的时候这样开发,当需要拆分成微服务的时候就可以直接将业务包拆分出去,因为每个业务包里面就已经包含了所有的当前业务的关联业务类。

避免多边界业务的关联查询

单表关联由于业务需要而且简单方便易使用,所以多表关联查询在单体服务中是普遍存在的,如果我们后期不需要做服务拆分则可以不需要考虑这方面的限制。

但是如果后期有微服务的规划,那么单体服务的时候如果没有做这个方面的限制,mybatis 的 mapper.xml中有过多的多表关联查询,这些关联查询会严重影响服务拆分的进度和复杂度。

如果同属于一个业务领域则可以使用关联查询,而那些微服务拆分后属于不同领域的业务则应避免使用多表关联查询,因为不同的业务领域后期会被隔离拆分到不同的服务当中,即数据库表都是分布在不同的服务器上,所有服务之间都是通过RPC方式进行通信,关联查询这时是无法处理的。

Controller层尽量不做业务逻辑处理

常看到很多 coder 会在Controller 层做一些业务处理,个人认为这是很不规范的。Controller层是控制层,是和前端进行数据转换的,这里我们应该只做请求的接受和返回,也无需做一些异常的try...catch...的捕获,异常可以通过全局通用拦截器统一进行拦截然后返回给前端异常提示语,提升代码的简洁性。

所有的参数校验也放到 service层,因为如果服务内部调用也可以使用提高代码的共用度。当然分层领域模型最好也能区分开,

DO(Data Object):此对象与数据库表结构--对应,通过DAO层向上传输数据源对象。

DTO(Date Transfer Object):数据传输对象,service或Manager向外传输的对象。

VO(View Object):显示层的对象,通常是Web向模板渲染引擎层传输的对象。

这样区分开的好处是,当你需要对展示层数据进行特殊定制化的时候可以灵活变通,例如针对用户隐私信息身份证号,手机号码脱敏处理,或者用户ID加密显示等。

最后就是统一通用返回类了,通过这种格式的封装我们将数据格式进行全局格式化,这里的状态码可以自己设计的更详细一点。

public class CommonResult { public static final String CODE_SUCCESS = "0"; public static final String CODE_Failed = "9999"; private String code; private T data; private String msg; private CommonResult(String code, T data, String msg) { this.code = code; this.data = data; this.msg = msg; } public boolean isSuccess() { return CODE_SUCCESS.equals(code); } public static CommonResult success() { return new CommonResult(CODE_SUCCESS, null, null); } public static CommonResult success(T data) { return new CommonResult(CODE_SUCCESS, data, null); } public static CommonResult success(T data, String msg) { return new CommonResult(CODE_SUCCESS, data, msg); } public static CommonResult Failed() { return new CommonResult(CODE_Failed, null, null); } public static CommonResult Failed(String errorCode, String msg) { return new CommonResult(errorCode, null, msg); } public static CommonResult Failed(String msg) { return new CommonResult(CODE_Failed, null, msg); } public static CommonResult Failed(T data, String msg) { return new CommonResult(CODE_Failed, data, msg); } public static CommonResult Failed(String errorCode, T data, String msg) { return new CommonResult(errorCode, data, msg); } // 省略 setter、getter }

后记

以上只是列举了单体服务未来规划做微服务时需要注意的一部分简单内容,每个人在做单体架构拆分成微服务的时候都会踩到各种各样的坑,这些坑成了我们的开发经验,有了这些坑就会形成注意点,在我们下次开发时就会具有指导意义。也许我们程序员就是在踩坑和填坑的过程中成长壮大起来的。

以上就是Java从单体架构升级到微服务要注意的一些问题的详细内容,更多关于Java的资料请关注小编其它相关文章!

laravel 项目部署到生产环境

laravel 项目部署到生产环境

#!/bin/bash 
 php artisan down 
 git pull 
 composer clearcache && composer dumpautoload 
 php artisan config:cache 
 php artisan optimize 
 php artisan up

或者

 

初始化部署:

git clone git@...
#克隆仓库获得代码
chown -R www:www *
#这里设置为nginx/Apache的实际的用户组,不知道是什么就删掉这行吧
chmod -R 755 *
chmod -R 777 storage
#设置权限
composer install --no-dev
#安装依赖

修改.env,设置
APP_ENV=production
APP_DEBUG=false
以及其它的一些配置,确保MySQL等连接正常

php artisan migrate
php artisan key:generate
#框架初始化

到这里第一次的部署就完成了。不出意外程序已经能够正常跑起来了。

 

代码更新的部署:
先自己手动备份数据库的全部内容,可以用MySQL的Dump

php artisan down
#停掉网站
git pull
php artisan migrate
#更新代码及数据库
php artisan clear-compiled
php artisan cache:clear
php artisan config:cache
php artisan optimize
composer dump-autoload --optimize
#各种清空缓存和重建缓存
php artisan up
#关闭维护状态,更新完毕

 

我们今天的关于总结Laravel项目部署到线上需要注意的一些问题laravel项目部署到服务器的分享已经告一段落,感谢您的关注,如果您想了解更多关于ANDROID动态加载 使用SO库时要注意的一些问题、C/C++ 语言要注意的一些问题、Java从单体架构升级到微服务要注意的一些问题、laravel 项目部署到生产环境的相关信息,请在本站查询。

本文标签: