精华内容
下载资源
问答
  • 工作那些事(二)应聘时填写个人信息ABCD

    千次阅读 多人点赞 2013-09-26 17:21:09
    应聘时填写个人信息ABCD: 公司A:填写应聘人员登记表(在前台的那种),内容包括: 姓名、时间、电话、身份证号码、事由(当然是面试)。 公司B:填写应聘人员登记表(作为入职的那种)

    先看看都有那些:

    公司A:

    填写来访人员登记表(在前台的那种),内容包括:

    姓名、时间、电话、职位


    公司B:

    填写来访人员登记表(在前台的那种),内容包括:

    姓名、时间、电话、身份证号码()、事由(当然是面试)。

    碰到一个公司要求这样。


    公司C:

    1、填写来访人员登记表(在前台的那种),内容包括:

    姓名、时间、电话、职位。


    2、填写应聘人员登记表(作为入职的那种),内容包括:

    姓名、年龄、身高、出生年月、学习经历、工作经历(公司、职位、领导姓名、电话)、离职原因、期望待遇等。

    这种是比较常见的。


    公司D:

    1、同B。

    2、包括B的2,还包括:

    你的工作原则性、价值观、职业规划、因工作需要是够提供深户担保、是否接受加班、你能创造的价值与贡献等。


            个人倾向于A公司的做法(仅限于技术方面的),其他的信息可以在笔试过后,人事面试的时候填写,这个时候,最起码双方有了继续下去的基础。首先,简历里的信息已经包含了基本信息:年龄、身高、出生年月等,没必要;其次,这只是初次见面,存在的可能很多,可能笔试都不过、可能这个公司也是应聘者N中之一,不确定性;最后,其实也是最重要的,填写项目多了,要用的时间就长。技术应聘首当其冲的是什么,是笔试,公司招不招应聘者,主要参考的也是笔试的结果,当然有人要说,现在笔试题网上搜一搜,什么样的都有。是的,没错,不过这就是另外一个话题了,公司如何出笔试题,笔试题也可以反映公司的某些文化。


           如果有公司技术总监或者人事总监,看到此文章,也麻烦思考一下这个问题:初次应聘时,哪些信息是必须填写的?这个其实也可以侧面反映公司的做事效率、流程等一些问题。





    展开全文
  • 表单格式自动填写小软件

    热门讨论 2012-09-15 03:41:22
    现在,您可以简化您的工作 - 只需选择你想自动填写和提交,填写??并提交表格周期之间设置延迟组的形式。 FormAutoFill支持一个以上的提交按钮的形式。 所以,你可以很容易地选择提交按钮,你想同时申请“填写和...
  • Spring Security 工作原理概览

    万次阅读 多人点赞 2019-04-27 08:02:58
    RememberMeAuthenticationFilter:当用户没有登录而直接访问资源, 从 cookie 里找出用户的信息, 如果 Spring Security 能够识别出用户提供的remember me cookie, 用户将不必填写用户名和密码, 而是直接登录进入...

    本文由读者 muggle 投稿,muggle 是一位具备极客精神的90后单身老实猿,对 Spring Security 有丰富的使用经验,muggle 个人博客地址是 https://muggle0.github.io。

    Security 原理分析

    SpringSecurity 过滤器链

    SpringSecurity 采用的是责任链的设计模式,它有一条很长的过滤器链。现在对这条过滤器链的各个进行说明:

    1. WebAsyncManagerIntegrationFilter:将 Security 上下文与 Spring Web 中用于处理异步请求映射的 WebAsyncManager 进行集成。

    2. SecurityContextPersistenceFilter:在每次请求处理之前将该请求相关的安全上下文信息加载到 SecurityContextHolder 中,然后在该次请求处理完成之后,将 SecurityContextHolder 中关于这次请求的信息存储到一个“仓储”中,然后将 SecurityContextHolder 中的信息清除,例如在Session中维护一个用户的安全信息就是这个过滤器处理的。

    3. HeaderWriterFilter:用于将头信息加入响应中。

    4. CsrfFilter:用于处理跨站请求伪造。

    5. LogoutFilter:用于处理退出登录。

    6. UsernamePasswordAuthenticationFilter:用于处理基于表单的登录请求,从表单中获取用户名和密码。默认情况下处理来自 /login 的请求。从表单中获取用户名和密码时,默认使用的表单 name 值为 username 和 password,这两个值可以通过设置这个过滤器的usernameParameter 和 passwordParameter 两个参数的值进行修改。

    7. DefaultLoginPageGeneratingFilter:如果没有配置登录页面,那系统初始化时就会配置这个过滤器,并且用于在需要进行登录时生成一个登录表单页面。

    8. BasicAuthenticationFilter:检测和处理 http basic 认证。

    9. RequestCacheAwareFilter:用来处理请求的缓存。

    10. SecurityContextHolderAwareRequestFilter:主要是包装请求对象request。

    11. AnonymousAuthenticationFilter:检测 SecurityContextHolder 中是否存在 Authentication 对象,如果不存在为其提供一个匿名 Authentication。

    12. SessionManagementFilter:管理 session 的过滤器

    13. ExceptionTranslationFilter:处理 AccessDeniedException 和 AuthenticationException 异常。

    14. FilterSecurityInterceptor:可以看做过滤器链的出口。

    15. RememberMeAuthenticationFilter:当用户没有登录而直接访问资源时, 从 cookie 里找出用户的信息, 如果 Spring Security 能够识别出用户提供的remember me cookie, 用户将不必填写用户名和密码, 而是直接登录进入系统,该过滤器默认不开启。

    SpringSecurity 流程图

    先来看下面一个 Spring Security 执行流程图,只要把 SpringSecurity 的执行过程弄明白了,这个框架就会变得很简单:

    640?wx_fmt=png

    流程说明

    1. 客户端发起一个请求,进入 Security 过滤器链。

    2. 当到 LogoutFilter 的时候判断是否是登出路径,如果是登出路径则到 logoutHandler ,如果登出成功则到 logoutSuccessHandler 登出成功处理,如果登出失败则由 ExceptionTranslationFilter ;如果不是登出路径则直接进入下一个过滤器。

    3. 当到 UsernamePasswordAuthenticationFilter 的时候判断是否为登录路径,如果是,则进入该过滤器进行登录操作,如果登录失败则到 AuthenticationFailureHandler 登录失败处理器处理,如果登录成功则到 AuthenticationSuccessHandler 登录成功处理器处理,如果不是登录请求则不进入该过滤器。

    4. 当到 FilterSecurityInterceptor 的时候会拿到 uri ,根据 uri 去找对应的鉴权管理器,鉴权管理器做鉴权工作,鉴权成功则到 Controller 层否则到 AccessDeniedHandler 鉴权失败处理器处理。

    Security 配置

    WebSecurityConfigurerAdapter 这个类里面可以完成上述流程图的所有配置

    配置类伪代码

    @Configuration	
    @EnableWebSecurity	
    public class SecurityConfig extends WebSecurityConfigurerAdapter {	
        @Override	
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {	
            auth.userDetailsService(userDetailService).passwordEncoder(new BCryptPasswordEncoder());	
        }	
        @Override	
        public void configure(WebSecurity web) throws Exception {	
            web.ignoring().antMatchers("/resources/**/*.html", "/resources/**/*.js");	
        }	
        @Override	
        protected void configure(HttpSecurity http) throws Exception {	
           http	
           .formLogin()	
           .loginPage("/login_page")	
           .passwordParameter("username")	
           .passwordParameter("password")	
           .loginProcessingUrl("/sign_in")	
           .permitAll()	
           .and().authorizeRequests().antMatchers("/test").hasRole("test")	
           .anyRequest().authenticated().accessDecisionManager(accessDecisionManager())	
           .and().logout().logoutSuccessHandler(new MyLogoutSuccessHandler())	
           .and().csrf().disable();	
           http.addFilterAt(getAuthenticationFilter(),UsernamePasswordAuthenticationFilter.class);	
           http.exceptionHandling().accessDeniedHandler(new MyAccessDeniedHandler());	
           http.addFilterAfter(new MyFittler(), LogoutFilter.class);	
        }	
    }

    配置简介

    • configure(AuthenticationManagerBuilder auth)

    AuthenticationManager 的建造器,配置 AuthenticationManagerBuilder 会让Security 自动构建一个 AuthenticationManager(该类的功能参考流程图);如果想要使用该功能你需要配置一个 UserDetailService 和 PasswordEncoder。UserDetailsService 用于在认证器中根据用户传过来的用户名查找一个用户, PasswordEncoder 用于密码的加密与比对,我们存储用户密码的时候用PasswordEncoder.encode() 加密存储,在认证器里会调用 PasswordEncoder.matches() 方法进行密码比对。如果重写了该方法,Security 会启用 DaoAuthenticationProvider 这个认证器,该认证就是先调用 UserDetailsService.loadUserByUsername 然后使用 PasswordEncoder.matches() 进行密码比对,如果认证成功成功则返回一个 Authentication 对象。

    • configure(WebSecurity web)

    这个配置方法用于配置静态资源的处理方式,可使用 Ant 匹配规则。

    • configure(HttpSecurity http)

    这个配置方法是最关键的方法,也是最复杂的方法。我们慢慢掰开来说:

    http	
    .formLogin()	
    .loginPage("/login_page")	
    .passwordParameter("username")	
    .passwordParameter("password")	
    .loginProcessingUrl("/sign_in")	
    .permitAll()

    这是配置登录相关的操作从方法名可知,配置了登录页请求路径,密码属性名,用户名属性名,和登录请求路径,permitAll()代表任意用户可访问。

    http	
    .authorizeRequests()	
    .antMatchers("/test").hasRole("test")	
    .anyRequest().authenticated()	
    .accessDecisionManager(accessDecisionManager());

    以上配置是权限相关的配置,配置了一个 /test url 该有什么权限才能访问, anyRequest() 表示所有请求,authenticated() 表示已登录用户才能访问, accessDecisionManager() 表示绑定在 url 上的鉴权管理器

    为了对比,现在贴出另一个权限配置清单:

    http.authorizeRequests()	
    .antMatchers("/tets_a/**","/test_b/**").hasRole("test")	
    .antMatchers("/a/**","/b/**").authenticated()	
    .accessDecisionManager(accessDecisionManager())

    我们可以看到权限配置的自由度很高,鉴权管理器可以绑定到任意 url 上;而且可以硬编码各种 url 权限:

    http	
    .logout()	
    .logoutUrl("/logout")	
    .logoutSuccessHandler(new MyLogoutSuccessHandler())

    登出相关配置,这里配置了登出 url 和登出成功处理器:

    http	
    .exceptionHandling()	
    .accessDeniedHandler(new MyAccessDeniedHandler());

    上面代码是配置鉴权失败的处理器。

    http.addFilterAfter(new MyFittler(), LogoutFilter.class);	
    http.addFilterAt(getAuthenticationFilter(),UsernamePasswordAuthenticationFilter.class);

    上面代码展示如何在过滤器链中插入自己的过滤器,addFilterBefore 加在对应的过滤器之前,addFilterAfter 加在对应的过滤器之后,addFilterAt 加在过滤器同一位置,事实上框架原有的 Filter 在启动 HttpSecurity 配置的过程中,都由框架完成了其一定程度上固定的配置,是不允许更改替换的。根据测试结果来看,调用 addFilterAt 方法插入的 Filter ,会在这个位置上的原有 Filter 之前执行。

    注:关于 HttpSecurity 使用的是链式编程,其中 http.xxxx.and.yyyyy 这种写法和 http.xxxx;http.yyyy 写法意义一样。

    • 自定义 AuthenticationManager 和 AccessDecisionManager

    重写 authenticationManagerBean() 方法,并构造一个 authenticationManager:

    @Override	
    public AuthenticationManager authenticationManagerBean() throws Exception {	
        ProviderManager authenticationManager = new ProviderManager(Arrays.asLis(getMyAuthenticationProvider(),daoAuthenticationProvider()));	
        return authenticationManager;	
    }

    我这里给 authenticationManager 配置了两个认证器,执行过程参考流程图。

    定义构造AccessDecisionManager的方法并在配置类中调用,配置参考 configure(HttpSecurity http) 说明:

    public AccessDecisionManager accessDecisionManager(){	
        List<AccessDecisionVoter<? extends Object>> decisionVoters	
                = Arrays.asList(	
                new MyExpressionVoter(),	
                new WebExpressionVoter(),	
                new RoleVoter(),	
                new AuthenticatedVoter());	
        return new UnanimousBased(decisionVoters);	
    }

    投票管理器会收集投票器投票结果做统计,最终结果大于等于0代表通过;每个投票器会返回三个结果:-1(反对),0(通过),1(赞成)。

    Security 权限系统

    • UserDetails

    Security 中的用户接口,我们自定义用户类要实现该接口。

    • GrantedAuthority

    Security 中的用户权限接口,自定义权限需要实现该接口:

    public class MyGrantedAuthority implements GrantedAuthority {	
        private String authority;	
    }

    authority 表示权限字段,需要注意的是在 config 中配置的权限会被加上 ROLE_ 前缀,比如我们的配置 authorizeRequests().antMatchers("/test").hasRole("test"),配置了一个 test 权限但我们存储的权限字段(authority)应该是 ROLE_test

    • UserDetailsService

    Security 中的用户 Service,自定义用户服务类需要实现该接口:

    @Service	
    public class MyUserDetailService implements UserDetailsService {	
        @Override	
        public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {	
          return.....	
        }	
    }

    loadUserByUsername的作用在上文中已经说明,就是根据用户名查询用户对象。

    • SecurityContextHolder

    用户在完成登录后 Security 会将用户信息存储到这个类中,之后其他流程需要得到用户信息时都是从这个类中获得,用户信息被封装成 SecurityContext ,而实际存储的类是 SecurityContextHolderStrategy ,默认的SecurityContextHolderStrategy 实现类是 ThreadLocalSecurityContextHolderStrategy 它使用了ThreadLocal来存储了用户信息。

    手动填充 SecurityContextHolder 示例:

    UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken("test","test",list);	
    SecurityContextHolder.getContext().setAuthentication(token);

    对于使用 token 鉴权的系统,我们就可以验证token后手动填充SecurityContextHolder,填充时机只要在执行投票器之前即可,或者干脆可以在投票器中填充,然后在登出操作中清空SecurityContextHolder。

    Security 扩展

    Security 可扩展的有

    1. 鉴权失败处理器

    2. 验证器

    3. 登录成功处理器

    4. 投票器

    5. 自定义token处理过滤器

    6. 登出成功处理器

    7. 登录失败处理器

    8. 自定义 UsernamePasswordAuthenticationFilter

    • 鉴权失败处理器

    Security 鉴权失败默认跳转登录页面,我们可以实现 AccessDeniedHandler 接口,重写 handle() 方法来自定义处理逻辑;然后参考配置类说明将处理器加入到配置当中。

    • 验证器

    实现 AuthenticationProvider 接口来实现自己验证逻辑。需要注意的是在这个类里面就算你抛出异常,也不会中断验证流程,而是算你验证失败,我们由流程图知道,只要有一个验证器验证成功,就算验证成功,所以你需要留意这一点。

    • 登录成功处理器

    在 Security 中验证成功默认跳转到上一次请求页面或者路径为 "/" 的页面,我们同样可以自定义:继承 SimpleUrlAuthenticationSuccessHandler 这个类或者实现 AuthenticationSuccessHandler 接口。我这里建议采用继承的方式,SimpleUrlAuthenticationSuccessHandler 是默认的处理器,采用继承可以契合里氏替换原则,提高代码的复用性和避免不必要的错误。

    • 投票器

    投票器可继承 WebExpressionVoter 或者实现 AccessDecisionVoter接口;WebExpressionVoter 是 Security 默认的投票器;我这里同样建议采用继承的方式;添加到配置的方式参考 上文;

    注意:投票器 vote 方法返回一个int值;-1代表反对,0代表弃权,1代表赞成;投票管理器收集投票结果,如果最终结果大于等于0则放行该请求。

    • 自定义token处理过滤器

    自定义 token 处理器继承自 OncePerRequestFilter 或者 GenericFilterBean 或者 Filter 都可以,在这个处理器里面需要完成的逻辑是:获取请求里的 token,验证 token 是否合法然后填充 SecurityContextHolder ,虽然说过滤器只要添加在投票器之前就可以,但我这里还是建议添加在 http.addFilterAfter(new MyFittler(), LogoutFilter.class);

    • 登出成功处理器

    实现LogoutSuccessHandler接口,添加到配置的方式参考上文。

    • 登录失败处理器

    登录失败默认跳转到登录页,我们同样可以自定义。继承 SimpleUrlAuthenticationFailureHandler 或者实现 AuthenticationFailureHandler,建议采用继承。

    • 自定义UsernamePasswordAuthenticationFilter

    我们自定义UsernamePasswordAuthenticationFilter可以极大提高我们 Security的灵活性(比如添加验证验证码是否正确的功能)。

    我们直接继承 UsernamePasswordAuthenticationFilter ,然后在配置类中初始化这个过滤器,给这个过滤器添加登录失败处理器,登录成功处理器,登录管理器,登录请求 url 。

    这里配置略微复杂,贴一下代码清单

    初始化过滤器:

    MyUsernamePasswordAuthenticationFilte getAuthenticationFilter(){	
        MyUsernamePasswordAuthenticationFilter myUsernamePasswordAuthenticationFilter = new MyUsernamePasswordAuthenticationFilter(redisService);	
        myUsernamePasswordAuthenticationFilter.setAuthenticationFailureHandler(new MyUrlAuthenticationFailureHandler());	
        myUsernamePasswordAuthenticationFilter.setAuthenticationSuccessHandler(new MyAuthenticationSuccessHandler());	
        myUsernamePasswordAuthenticationFilter.setFilterProcessesUrl("/sign_in");	
        myUsernamePasswordAuthenticationFilter.setAuthenticationManager(getAuthenticationManager());	
        return myUsernamePasswordAuthenticationFilter;	
    }

    添加到配置:

    http.addFilterAt(getAuthenticationFilter(),UsernamePasswordAuthenticationFilter.class);

    总结

    对于 Security 的扩展配置关键在于 configure(HttpSecurityhttp) 方法;扩展认证方式可以自定义 authenticationManager 并加入自己验证器,在验证器中抛出异常不会终止验证流程;扩展鉴权方式可以自定义 accessDecisionManager 然后添加自己的投票器并绑定到对应的 url(url 匹配方式为 ant)上,投票器 vote(Authenticationauthentication,FilterInvocationfi,Collection<ConfigAttribute>attributes) 方法返回值为三种:-1 0 1,分别表示反对弃权赞成。

    对于 token 认证的校验方式,可以暴露一个获取的接口,或者重写 UsernamePasswordAuthenticationFilter 过滤器和扩展登录成功处理器来获取 token,然后在 LogoutFilter 之后添加一个自定义过滤器,用于校验和填充 SecurityContextHolder。

    另外,Security 的处理器大部分都是重定向的,我们的项目如果是前后端分离的话,我们希望无论什么情况都返回 json ,那么就需要重写各个处理器了。


    640 640关注牧码小子,后台回复 Java ,领取松哥为你精心准备的Java干货! 640 

    往期文章一览

    1、工作之余,你是怎么提高技术的?

    2、两年了,我写了这些干货!

    3、想和大家谈一点合作

    4、一个Java程序猿眼中的前后端分离以及Vue.js入门

    5、跟着平台混了四年,现在要单飞了!

    640?wx_fmt=png你点的每个在看,我都认真当成了喜欢


    展开全文
  • 漫谈程序员系列:找工作的辟邪剑谱

    万次阅读 多人点赞 2014-12-09 08:05:27
    去咖啡厅喝个咖啡或者酒吧喝个小酒就把工作搞定的程序员可以洗洗睡了,记住回自己家睡……别 if - else 了,选择太多不是好事儿……

        我原来面试过一个哥们儿,半年内换了七家公司,我表示膜拜。

        还有一些哥们儿,七年不换工作。这得是多稳定的工作啊,七年之痒都扛过去了,真心不错。

        这都是极端,一般的程序员,可能会一年半载或者三两年换一次工作。在一个公司干上老多年老多年的程序员,不是成了大牛公司离不开,就是成了废柴哪里也去不了。我说得太极端了一些,可能还有到哪儿都无所谓的,能待着就待着这种的吧。

        这次我们要说的是找工作,没错儿,就是找工作。

        去咖啡厅喝个咖啡或者酒吧喝个小酒就把工作搞定的程序员可以洗洗睡了,记住回自己家睡……别 if - else 了,选择太多不是好事儿……

    面试时如何展现自己

        其实我这人比较懵懂,一直也没太搞明白如何展现自己的优点,甚至自己有什么优点也不甚明了。还是看一张来自互联网的图片吧:


        好像我越来越邪恶了……

        这幅图绝对木有讽刺那谁谁的意思,表误解。它的意思其实是说,面试时你一定要展现出公司希望看到的那一面,不一定是事业线……再说,作为程序员,大多也没这种事业线……

        我发现真是写代码写惯了,总想把一句话写得没歧义,真受不了……其实话一出口就是别人的事儿啦,谁愿意怎么理解就怎么理解,说话的人完全管不了了……

        有人问,我怎么知道公司希望我怎么样呢?

        其实要你自己做功课的,有几点是必须的:

    • 仔细研究职位要求和工作内容,梳理自己与这些要求吻合的那部分能力、经历、知识……
    • 仔细研究公司的产品或者项目,了解公司所在行业的背景知识、市场情况、公司的竞争对手的情况
    • 揣摩这家公司招你过去到底想解决什么问题

    目标

        千岩万壑不辞劳,

        远看方知出处高。

        溪涧岂能留得住,

        终归大海作波涛。

        这是唐代的李忱的诗,有另外一首诗刚好与它辉映:

    Liberty and love

    These two I must have

    For love ,I will

    sacrifice my life;

    For liberty,I will

    sacrifice my love

        相信你看到这一篇,已经知道我总是在扯淡,能扯多远扯多远。

        其实这两首诗没别的意思,白水文的解释是酱紫的:人生要有个目标,有所追求。

        OK ,对于程序员来讲,也是一样的,我们也是人么,并非《未来战士》里流体金属造就的变异物种,当然也是有目标的。

        生活的目标是什么?上帝把我们抛到这个尘世上来,除了让我们努力活得好一些之外,真不知道他还赋予了我们什么终极意义?

        生活的目标决定了你工作的目标,So,搞明白这两点其实还是有好处的。

        你希求什么,决定了你要找什么样的工作、什么样的公司。

        几个月前我离职时,我的老板对我说:到了这个年龄,你缺的不是一份工作。

        我深以为然:工作好找,找到能解决你为什么工作的工作难找。

    简历

        当你开始更新简历时,大概有两种情况:一种是想好了要去做什么,一种是眼下的事儿实在干不下去了。换句话说,一个是主动去追求,一个是被逼无奈要换个环境。

        不管哪一种,要撰写简历是免不了的。

        我换过七次工作,写过很多次简历。我被别人面试过不下二十次。我看过的简历超过300份,面试过的程序员不下100个。我想我可以从我的角度来谈谈简历里有什么需要注意的事儿,注意,绝不是灵丹妙药,很可能是票房毒药。

    • 为每一个公司的每一个岗位准备一份简历,针对岗位要求来修改你的简历,别怕麻烦,一份简历通吃天下的作法只能说明你是个懒蛋,一点儿都不认真,那种把职位空出来,到哪个公司应聘就填写哪个职位的哥们儿,我看到直接就拒了……
    • 不要超过2页,我看简历一般不超过两分钟……有的人更短,只有30秒不到就下决定了
    • 教育经历要写明,很多公司会用学校和学历筛掉一大批应聘者,因为他们相信,如果你能学能干,考上好学校不是问题,这可能有点儿以偏概全……
    • 与对方要求相关的技能突出描述,放在所有工作经历之前,很多公司招人时打的算盘是“招之即来来则能战战则能胜”,你要不突出你的相关经验,八成没戏……
    • 最近的工作在前,过于久远的工作经历,提一下就是了
    • 项目里说明自己的角色、用到的技术、负责的模块、完成的结果,如果有攻克难关,要表述出来
    • 如果不是漂亮女生,就不要贴照片了……

        其实度娘知道很多简历的事儿,悄悄问问她吧。

        一份好的简历会带给你笔试或面试的机会,值得下功夫去琢磨如何在简历中展现你独特的一面。

    关于内部推荐

        其实还有一些其它的途径可以让你顺利闯过简历筛选这一关:内部推荐。很多公司信这个,认为自己的员工不会推荐不靠谱的人。我工作过的公司,如果你推荐的人被录用并且成功通过试用期,你会获得一千大洋的伯乐奖金。我媳妇的公司也有类似的机制。所以,如果你能找一个目标公司的员工来推荐你,那你就踏出了成功的一步。

        之所以内部推荐成功率高,是因为简历、笔试、面试这样一个流程,公司和应聘者之间还是不能够真的相互了解,这是因为语言是一种障碍,你说很多话,想要沟通,其实却会阻碍沟通。因为应聘者会不自觉的美化自己,公司也会美化自己,大家看到的都是恋爱前的样子,实际上吃、住、睡在一起了,很可能发现没有原来那种美好,很快就又分道扬镳了。试用期虽然双方自己,但对大家都是巨大的成本。而内部推荐,它潜在的逻辑是:推荐求职者的员工应当对求职者有所了解,同时也对公司的职位要求了解,推荐的是相对match的人;反过来,求职者也会通过推荐者了解公司的情况,消除求职者的盲目性;最终双方能够合拍的概率会大很多。

        当然你不是每次都能找到一个人来推荐你,因为有的公司没有内部推荐这一说,也有的公司里没有你认识的人。但如果可能,还是要尝试这种途径。

        六度空间理论(又名六度分隔理论)说:你至多只要通过六个人就能认识全世界的任意一个人。所以,理论上你可以在任何一个公司找到可以推荐你的人,只看你愿不愿意花心思。不要担心别人不愿意推荐你,多数人在帮助别人时是感到快乐的,不是有个词儿叫作“助人为乐”吗?当然,你可别存着坑人家的心,明明你的目标是要用自己强大的黑暗能力毁掉这家公司,却信誓旦旦地说你要帮助公司飞上青天,那样就太不厚道了。

    笔试


        有的公司喜欢笔试,通不过笔试你绝不可能成为他们的一员。

        还有的公司喜欢出一些自己的顶级程序员也答不上来的古怪难题(比如计算某句谣言对某个人造成的心理阴影面积之类的),他们的一大目的(也是部分老鸟程序员的乐趣)就是看着你出一些洋相,让你受一下挫折,打击一下你嚣张的气焰(想想那些监狱题材的影片里老犯人对新服刑的雏儿的折磨你就明白了),然后他们好自我优越一把。也许是我太邪恶,不惮以最坏的恶意来揣测别人吧。

        当然也有的公司不笔试,只面试。

        还有,有些公司的笔试是针对某一级别的求职者的……也许你现在到哪里都不用笔试喽。

        如果你必须笔试,我没什么好说的(多少年不做卷子了……学生时代的记忆已经模糊,像雾像雨又像风,也像帝国的霾……),只有一点,你还是要研究你应聘的职位要求,然后揣摩笔试的范围,多少会有一些用,比如他找 C++ 的码农,自然不会考你 Java 。

        世面上也有很多类似《C++笔试宝典》之类的书,会总结各类公司常出的笔试题并给出解法, Java 的也有,其它的有木有我不知道,我没看过……

    面试

        笔试之后就是面试了,也许你一个小时候后就会接到面试邀约,也许你等到花儿也谢了也没有音讯,反正大多数公司只会通知通过的那些求职者,对于被涮掉的,潜规则是无视。So,这一切其实不那么美好,也许你觉得自己题目答的不错,没100分也有99,那你就是抓破头皮也想不明白为什么不通知你面试了——其实有时候答的太好了是一种错,他们可能怀疑你利用智能手机在线求助,或者认为你熟读各种笔试宝典,或者认为你来公司笔试前踩了狗屎(要知道现在无证养狗的人很多,随地大小便的狗狗的数量呈指数级增长,已经快赶上随地大小便的人了),或者批阅试卷的人会认为你太优秀将来会对他们不利……总之各种可能吧,也可能是 HR 或者前台觉得一一回复应聘者过于麻烦……

        未知会导致焦虑,所以先贤们总是强调格物致知。焦虑是一种煎熬,如果你正巧背负各种金钱压力,缸中之米仅够三两日用,那你的焦虑就又会指数级增长,多一天你就会受不了……很多人在这种时候会对突然到来的机会有特别的好感,虽然不会像范进中举一样疯掉,却也会盲目的认为这家公司一定适合自己,不管怎样都希望能够快点到这家公司去,于是后来就又可能产生悲剧——干不了几天就一言不合一拍两散。

        程序员有一个问题,就是沟通障碍。这是双方的,是面试官和求职者都存在的问题:一面通常都是来自技术线的优秀程序员。如果你和面试官投缘,一见钟情,那恭喜你,你一张嘴他就知道你想说什么,你一撅屁股他就知道你拉什么屎,你99%可以通过面试。如果你和面试官不登对,一见面就气场不合,大眼瞪小眼,表面彬彬有礼,暗地里已经刀枪剑戟了,那你99%会被咔掉。更多的时候,你和面试官对对方都没什么感觉,既不来电也不来厌,你们需要用语言来沟通,在一问一答中表现自己。

        因为面试者和求职者地位上的不平等,会对面试过程产生一些微妙的影响。这里建议你站在公司的角度来考虑一下,其实他们也是有求于人,并不是你热脸要去贴冷屁股。多数公司愿意走面试环节,都是因为真的需要人,不然劳民伤财为哪般呢。如果你想明白这一点,就无需紧张,也无需仰视那些或牛X或装B或让人膜拜的面试官了,你可以放平心态,侃侃而谈。如果你不善言辞,也可以自然地展现你的实力,就用你以前工作的方式来展现你自己。

        很多自认为能力不错的程序员往往在面试时折戟沉沙,我也是这样。这里面的一大原因是我们不知道如何展现自己的能力,是滔滔不绝雄辩天下,还是一问一答诚实木讷?这是大问题,语言本身就是迷雾,会给沟通带来很多障碍,你心里想的是这样,说出来就带了起码20%的误差,而听的人又有自己的过滤器,他们只会听出自己想听的那部分忽略你想说的那部分,所以要想彼此理解,是天大的困难。“听话听音”这个词儿就是描述使用语言沟通的困境的。

        那么语言究竟能不能表达出程序员的能力来?答案是不能!

        因为软件开发的特性,程序员做得多说得少,久而久之更善于用简单直接的逻辑来表达想法,不善于琢磨话语背后的意思。而面试却恰恰需要琢磨话外音:到底面试官问这个问题的意图是什么?他希望考察应聘者哪一方面的技能和见解?他某一句话对我是肯定还是鄙视还是无所谓我还要不要继续展开来说?所有这些不确定,会让求职者产生焦虑和惶惑,原本可以表现十分的,只能表现六分。当然也有一些人很牛,语言能力很强,心脏上的窟窿特别多,眼睛也很毒,脑子又比较灵,兼之略懂读心术,能够看透面试官转笔筒或者跷二郎腿背后的意思,也能够理解面试官语气的变化代表的心思转折。这样的人是人才,可以通过大部分的面试。

        生活在零壹世界里,我很容易采用二分法。其实程序员有两种,一种是茶壶里煮饺子,有才说不出;一种是说得比做得多,一分的才可以展现出十分来。到底哪种好,看站在谁的角度上看了,反正这两种类型的人大多相互看不起。而对于公司来讲,其实两种人都需要。

        既然对话不能了解一个程序员真正的实力,那为什么还要走这一套程序呢?答案是:没有更可行的方案。

        现代人在生活中已经比较开放了,没结婚就可以耍流氓了,觉得不合适还可以换个对象继续耍。而招聘与求职这么重要的事情上,却保守得紧,不肯采用更合适的方式方法,比如让一个程序员到公司干上几天试试双方是否合适。其实这种方式好处非常多,和试婚差不多,因为只有近距离接触我们才能看得更细致,避免那种远距离的看上去很美带来的悲剧。

        那么,究竟面试时,程序员该如何展现自己呢?

        崔健的《从头再来》里有一句词儿:“我越来越会沉默我越来越会胡说”。这也是我的状态。下面的话也是胡说:

    • 知道你擅长什么
    • 知道公司需要什么
    • 不要争辩、争执,哪怕对方误解了你
    • 知道面试官的身份,比如是 HR ,是程序员,是项目经理,部门经理,还是技术副总,这很重要,不同角色不同Level的人,听话的方式是不一样的,你需要根据这个来调整你说话的方式,以便对方可以听进去。下面就是switch-case语句了:
      • 程序员:把他当作你的同事,以讨论技术问题的方式交流,表现出你解决问题的能力
      • 一线技术管理者:多听,理解问题,知之为知之不知为不知,多数时候不要表现得比他们还聪明;表现出你团队协作的一面
      • 技术高层:表现出你自己的想法、看法,表现出你对公司技术方向、产品方向的了解与见解,当然也要表现你的技术能力,无须担心你太强吓住人家
      • HR:友善地和他们谈话,放下你程序员技术高于一切天下惟独我屌的想法,说人话别说黑话,别看不起不懂技术的人

        “别人说的话,随便听一听,自己做决定”。这是黄小琥的歌《没那么简单》里的词儿,也是你对上面的话应该采取的态度。

        面试可能有好几轮,有时一天就完了,当场就拿到 Offer 。有时会拖几个月甚至半年以上……此时你要有足够的耐心,相信好事多磨,要是等不了,就也没别的办法,可以签别的公司……

    为什么我会被 PASS 掉

        有时你还没到谈薪水的时候就石沉大海了……

        为什么?

        你很想知道答案,可是没人告诉你,你只能瞎琢磨。

        其实这是求职者和公司之间地位不对等的问题,公司觉得拒绝一个人可以不需要理由,就像你的女神不屌你一样。但对于求职者,如果能知道被拒的理由,会是非常好的一件事情。

        有几个常见的原因:

    • 面试官觉得你能力不行
    • 面试官觉得你能力太强
    • 面试官觉得你不稳定
    • 面试官觉得你个性太强
    • 面试官听不懂你说什么
    • 面试官觉得你听不懂他在说什么
    • 你工资要求太高,超过 HR 拿到的上线太多

        我这也是胡说,姑妄听之吧。

    谈薪水

        虽然我不善于谈这个,但我还是写了另一篇文章专门来谈这个问题……因为我是程序员,要覆盖得全面一些……

        这里就跳过去吧。

    骑驴找马

        裸辞是需要勇气的,几个月的空档期很多人承受不了。所以,相当一部分人是找着了下家才离职。那么,一边工作一边找下家,究竟有什么影响呢?

        招人,Android、Java、iOS,西安的,at me。

        有个词儿叫“心猿意马”,还有个俗语叫“身在曹营心在汉”,用在这种情况下都挺合适的。


        当你决定找工作时,对现在的工作就会产生懈怠,这是毋庸置疑的,别说你多有职业精神,都是空话,懈怠是必然的,即便你努力表现得和平常一样,但是一旦你想到不久就要离开,对很多事情就没那么尽心了:可能会把有些事儿推给别的同事;可能不再愿意为工作而加班;可能对手上的问题睁一只眼闭一只眼;可能会下意识地让自己空忙;可能难以集中心思来解决复杂的问题;对BOSS也没那么在意了……凡此种种,都会有外在的表现,发出各种信号,明眼人会看出来你已经动摇已经心思不属。真的,别以为神不知鬼不觉,人在做,天在看,你又不是演员,瞒得过谁呢?所以说,当你决定骑驴找马时,效率会下降,对公司的产出肯定会减少。

        那么对自己呢?其实也是一种煎熬,人都有站队的思维定势,希望在某一个集体中获取认同,不想像蝙蝠那样长久骑在墙上。你会担心现在公司的人看出你的马脚,会尽力掩饰蛛丝马迹,这会让你很累。你还会因为迟迟没找到马而焦虑:已经决定要走了,却找不到下家,这种情况会让你对自己的能力产生怀疑,让你有时沮丧有时疯狂有时黯然神伤……有时你还会觉得对不住现在的公司……你内心中有两个小人会经常打架搞得你疲惫不堪……当然也有强人,对这一切都不在乎,因为他认为到更好的公司单位更好的职位是天经地义的,绝不会像我们这种小工蜂一样拿忠字舞来束缚自己。

        不管怎么样,骑驴找马是一种常见的存在,你、我、他,可能每个人都曾经这样做,也相信别人会这么做, BOSS 们也常为此焦虑,但是,该发生的发生了,不改发生的也发生了,就这么着吧,不扯淡了,我唯一的建议是,无论怎样,还是要站好最后一班岗。


    -----------回顾分割线-------------------

    展开全文
  • Nginx工作原理和优化总结。

    万次阅读 多人点赞 2013-05-16 11:04:53
    在大多数场景下,默认的 NGINX 和 Linux 设置可以很好的工作,但要达到最佳性能,有些时候必须做些调整。首先我们先了解其工作原理。 1. Nginx的模块与工作原理 Nginx由内核和模块组成,其中,内核的设计非常...

          NGINX以高性能的负载均衡器,缓存,和web服务器闻名,驱动了全球超过 40% 最繁忙的网站。在大多数场景下,默认的 NGINX 和 Linux 设置可以很好的工作,但要达到最佳性能,有些时候必须做些调整。首先我们先了解其工作原理。

     

    一.  Nginx的模块与工作原理


    Nginx由内核和模块组成,其中,内核的设计非常微小和简洁,完成的工作也非常简单,仅仅通过查找配置文件将客户端请求映射到一个location block(location是Nginx配置中的一个指令,用于URL匹配),而在这个location中所配置的每个指令将会启动不同的模块去完成相应的工作。

    Nginx的模块从结构上分为核心模块、基础模块和第三方模块:

    核心模块:HTTP模块、EVENT模块和MAIL模块

    基础模块:HTTP Access模块、HTTP FastCGI模块、HTTP Proxy模块和HTTP Rewrite模块,

    第三方模块:HTTP Upstream Request Hash模块、Notice模块和HTTP Access Key模块。

    用户根据自己的需要开发的模块都属于第三方模块。正是有了这么多模块的支撑,Nginx的功能才会如此强大。

    Nginx的模块从功能上分为如下三类。

    Handlers(处理器模块)。此类模块直接处理请求,并进行输出内容和修改headers信息等操作。Handlers处理器模块一般只能有一个。

    Filters (过滤器模块)。此类模块主要对其他处理器模块输出的内容进行修改操作,最后由Nginx输出。

    Proxies (代理类模块)。此类模块是Nginx的HTTP Upstream之类的模块,这些模块主要与后端一些服务比如FastCGI等进行交互,实现服务代理和负载均衡等功能。

    图1-1展示了Nginx模块常规的HTTP请求和响应的过程。

          

    Nginx本身做的工作实际很少,当它接到一个HTTP请求时,它仅仅是通过查找配置文件将此次请求映射到一个location block,而此location中所配置的各个指令则会启动不同的模块去完成工作,因此模块可以看做Nginx真正的劳动工作者。通常一个location中的指令会涉及一个handler模块和多个filter模块(当然,多个location可以复用同一个模块)。handler模块负责处理请求,完成响应内容的生成,而filter模块对响应内容进行处理。

    Nginx的模块直接被编译进Nginx,因此属于静态编译方式。启动Nginx后,Nginx的模块被自动加载,不像Apache,首先将模块编译为一个so文件,然后在配置文件中指定是否进行加载。在解析配置文件时,Nginx的每个模块都有可能去处理某个请求,但是同一个处理请求只能由一个模块来完成。 

     

     

    二.  Nginx的进程模型


    在工作方式上,Nginx分为单工作进程和多工作进程两种模式。在单工作进程模式下,除主进程外,还有一个工作进程,工作进程是单线程的;在多工作进程模式下,每个工作进程包含多个线程。Nginx默认为单工作进程模式。

    Nginx在启动后,会有一个master进程和多个worker进程。

    1、master进程:管理进程

      master进程主要用来管理worker进程,具体包括如下4个主要功能:
            (1)接收来自外界的信号。
            (2)向各worker进程发送信号。
            (3)监控woker进程的运行状态。
            (4)当woker进程退出后(异常情况下),会自动重新启动新的woker进程。

    用户交互接口:master进程充当整个进程组与用户的交互接口,同时对进程进行监护。它不需要处理网络事件,不负责业务的执行,只会通过管理worker进程来实现重启服务、平滑升级、更换日志文件、配置文件实时生效等功能。

    重启work进程:我们要控制nginx,只需要通过kill向master进程发送信号就行了。比如kill -HUP pid,则是告诉nginx,从容地重启nginx,我们一般用这个信号来重启nginx,或重新加载配置,因为是从容地重启,因此服务是不中断的。

    master进程在接收到HUP信号后是怎么做的呢?

    1)、首先master进程在接到信号后,会先重新加载配置文件,然后再启动新的worker进程,并向所有老的worker进程发送信号,告诉他们可以光荣退休了。

    2)、新的worker在启动后,就开始接收新的请求,而老的worker在收到来自master的信号后,就不再接收新的请求,并且在当前进程中的所有未处理完的请求处理完成后,再退出。

    直接给master进程发送信号,这是比较传统的操作方式,nginx在0.8版本之后,引入了一系列命令行参数,来方便我们管理。比如,./nginx -s reload,就是来重启nginx,./nginx -s stop,就是来停止nginx的运行。如何做到的呢?我们还是拿reload来说,我们看到,执行命令时,我们是启动一个新的nginx进程,而新的nginx进程在解析到reload参数后,就知道我们的目的是控制nginx来重新加载配置文件了,它会向master进程发送信号,然后接下来的动作,就和我们直接向master进程发送信号一样了。

    2、worker进程:处理请求

    而基本的网络事件,则是放在worker进程中来处理了。多个worker进程之间是对等的,他们同等竞争来自客户端的请求,各进程互相之间是独立的。一个请求,只可能在一个worker进程中处理,一个worker进程,不可能处理其它进程的请求。worker进程的个数是可以设置的,一般我们会设置与机器cpu核数一致,这里面的原因与nginx的进程模型以及事件处理模型是分不开的。

    worker进程之间是平等的,每个进程,处理请求的机会也是一样的。当我们提供80端口的http服务时,一个连接请求过来,每个进程都有可能处理这个连接,怎么做到的呢?

     Nginx采用异步非阻塞的方式来处理网络事件,类似于Libevent,具体过程如下:

    1)接收请求:首先,每个worker进程都是从master进程fork过来,在master进程建立好需要listen的socket(listenfd)之后,然后再fork出多个worker进程。所有worker进程的listenfd会在新连接到来时变得可读,每个work进程都可以去accept这个socket(listenfd)。当一个client连接到来时,所有accept的work进程都会受到通知,但只有一个进程可以accept成功,其它的则会accept失败。为保证只有一个进程处理该连接,Nginx提供了一把共享锁accept_mutex来保证同一时刻只有一个work进程在accept连接。所有worker进程在注册listenfd读事件前抢accept_mutex,抢到互斥锁的那个进程注册listenfd读事件,在读事件里调用accept接受该连接。

    2)处理请求:当一个worker进程在accept这个连接之后,就开始读取请求,解析请求,处理请求,产生数据后,再返回给客户端,最后才断开连接,这样一个完整的请求就是这样的了。

    我们可以看到,一个请求,完全由worker进程来处理,而且只在一个worker进程中处理。worker进程之间是平等的,每个进程,处理请求的机会也是一样的。

    nginx的进程模型,可以由下图来表示:

    三.   Nginx为啥性能高-多进程IO模型


          参考http://mp.weixin.qq.com/s?__biz=MjM5NTg2NTU0Ng==&mid=407889757&idx=3&sn=cfa8a70a5fd2a674a91076f67808273c&scene=23&srcid=0401aeJQEraSG6uvLj69Hfve#rd

    1、nginx采用多进程模型好处

          首先,对于每个worker进程来说,独立的进程,不需要加锁,所以省掉了锁带来的开销,同时在编程以及问题查找时,也会方便很多。

          其次,采用独立的进程,可以让互相之间不会影响,一个进程退出后,其它进程还在工作,服务不会中断,master进程则很快启动新的worker进程。当然,worker进程的异常退出,肯定是程序有bug了,异常退出,会导致当前worker上的所有请求失败,不过不会影响到所有请求,所以降低了风险。

    2、nginx多进程事件模型:异步非阻塞


             虽然nginx采用多worker的方式来处理请求,每个worker里面只有一个主线程,那能够处理的并发数很有限啊,多少个worker就能处理多少个并发,何来高并发呢?非也,这就是nginx的高明之处,nginx采用了异步非阻塞的方式来处理请求,也就是说,nginx是可以同时处理成千上万个请求的。一个worker进程可以同时处理的请求数只受限于内存大小,而且在架构设计上,不同的worker进程之间处理并发请求时几乎没有同步锁的限制,worker进程通常不会进入睡眠状态,因此,当Nginx上的进程数与CPU核心数相等时(最好每一个worker进程都绑定特定的CPU核心),进程间切换的代价是最小的。

           而apache的常用工作方式(apache也有异步非阻塞版本,但因其与自带某些模块冲突,所以不常用),每个进程在一个时刻只处理一个请求因此,当并发数上到几千时,就同时有几千的进程在处理请求了。这对操作系统来说,是个不小的挑战,进程带来的内存占用非常大,进程的上下文切换带来的cpu开销很大,自然性能就上不去了,而这些开销完全是没有意义的。

         

     

             为什么nginx可以采用异步非阻塞的方式来处理呢,或者异步非阻塞到底是怎么回事呢?具体请看:使用 libevent 和 libev 提高网络应用性能——I/O模型演进变化史

             我们先回到原点,看看一个请求的完整过程:首先,请求过来,要建立连接,然后再接收数据,接收数据后,再发送数据。

             具体到系统底层,就是读写事件,而当读写事件没有准备好时,必然不可操作,如果不用非阻塞的方式来调用,那就得阻塞调用了,事件没有准备好,那就只能等了,等事件准备好了,你再继续吧。阻塞调用会进入内核等待,cpu就会让出去给别人用了,对单线程的worker来说,显然不合适,当网络事件越多时,大家都在等待呢,cpu空闲下来没人用,cpu利用率自然上不去了,更别谈高并发了。好吧,你说加进程数,这跟apache的线程模型有什么区别,注意,别增加无谓的上下文切换。所以,在nginx里面,最忌讳阻塞的系统调用了。不要阻塞,那就非阻塞喽。非阻塞就是,事件没有准备好,马上返回EAGAIN,告诉你,事件还没准备好呢,你慌什么,过会再来吧。好吧,你过一会,再来检查一下事件,直到事件准备好了为止,在这期间,你就可以先去做其它事情,然后再来看看事件好了没。虽然不阻塞了,但你得不时地过来检查一下事件的状态,你可以做更多的事情了,但带来的开销也是不小的。

    关于IO模型:http://blog.csdn.net/hguisu/article/details/7453390

           nginx支持的事件模型如下(nginx的wiki):

           Nginx支持如下处理连接的方法(I/O复用方法),这些方法可以通过use指令指定。

    • select– 标准方法。 如果当前平台没有更有效的方法,它是编译时默认的方法。你可以使用配置参数 –with-select_module 和 –without-select_module 来启用或禁用这个模块。
    • poll– 标准方法。 如果当前平台没有更有效的方法,它是编译时默认的方法。你可以使用配置参数 –with-poll_module 和 –without-poll_module 来启用或禁用这个模块。
    • kqueue– 高效的方法,使用于 FreeBSD 4.1+, OpenBSD 2.9+, NetBSD 2.0 和 MacOS X. 使用双处理器的MacOS X系统使用kqueue可能会造成内核崩溃。
    • epoll 高效的方法,使用于Linux内核2.6版本及以后的系统。在某些发行版本中,如SuSE 8.2, 有让2.4版本的内核支持epoll的补丁。
    • rtsig 可执行的实时信号,使用于Linux内核版本2.2.19以后的系统。默认情况下整个系统中不能出现大于1024个POSIX实时(排队)信号。这种情况 对于高负载的服务器来说是低效的;所以有必要通过调节内核参数 /proc/sys/kernel/rtsig-max 来增加队列的大小。可是从Linux内核版本2.6.6-mm2开始, 这个参数就不再使用了,并且对于每个进程有一个独立的信号队列,这个队列的大小可以用 RLIMIT_SIGPENDING 参数调节。当这个队列过于拥塞,nginx就放弃它并且开始使用 poll 方法来处理连接直到恢复正常。
    • /dev/poll – 高效的方法,使用于 Solaris 7 11/99+, HP/UX 11.22+ (eventport), IRIX 6.5.15+ 和 Tru64 UNIX 5.1A+.
    • eventport – 高效的方法,使用于 Solaris 10. 为了防止出现内核崩溃的问题, 有必要安装这个 安全补丁。

            在linux下面,只有epoll是高效的方法

            下面再来看看epoll到底是如何高效的
           Epoll是Linux内核为处理大批量句柄而作了改进的poll。 要使用epoll只需要这三个系统调用:epoll_create(2), epoll_ctl(2), epoll_wait(2)。它是在2.5.44内核中被引进的(epoll(4) is a new API introduced in Linux kernel 2.5.44),在2.6内核中得到广泛应用。

            epoll的优点

    • 支持一个进程打开大数目的socket描述符(FD)

            select 最不能忍受的是一个进程所打开的FD是有一定限制的,由FD_SETSIZE设置,默认值是2048。对于那些需要支持的上万连接数目的IM服务器来说显 然太少了。这时候你一是可以选择修改这个宏然后重新编译内核,不过资料也同时指出这样会带来网络效率的下降,二是可以选择多进程的解决方案(传统的 Apache方案),不过虽然linux上面创建进程的代价比较小,但仍旧是不可忽视的,加上进程间数据同步远比不上线程间同步的高效,所以也不是一种完 美的方案。不过 epoll则没有这个限制,它所支持的FD上限是最大可以打开文件的数目,这个数字一般远大于2048,举个例子,在1GB内存的机器上大约是10万左 右,具体数目可以cat /proc/sys/fs/file-max察看,一般来说这个数目和系统内存关系很大。

    • IO效率不随FD数目增加而线性下降

             传统的select/poll另一个致命弱点就是当你拥有一个很大的socket集合,不过由于网络延时,任一时间只有部分的socket是”活跃”的,但 是select/poll每次调用都会线性扫描全部的集合,导致效率呈现线性下降。但是epoll不存在这个问题,它只会对”活跃”的socket进行操 作—这是因为在内核实现中epoll是根据每个fd上面的callback函数实现的。那么,只有”活跃”的socket才会主动的去调用 callback函数,其他idle状态socket则不会,在这点上,epoll实现了一个”伪”AIO,因为这时候推动力在os内核。在一些 benchmark中,如果所有的socket基本上都是活跃的—比如一个高速LAN环境,epoll并不比select/poll有什么效率,相 反,如果过多使用epoll_ctl,效率相比还有稍微的下降。但是一旦使用idle connections模拟WAN环境,epoll的效率就远在select/poll之上了。

    • 使用mmap加速内核与用户空间的消息传递。

            这 点实际上涉及到epoll的具体实现了。无论是select,poll还是epoll都需要内核把FD消息通知给用户空间,如何避免不必要的内存拷贝就很 重要,在这点上,epoll是通过内核于用户空间mmap同一块内存实现的。而如果你想我一样从2.5内核就关注epoll的话,一定不会忘记手工 mmap这一步的。

    • 内核微调

             这一点其实不算epoll的优点了,而是整个linux平台的优点。也许你可以怀疑linux平台,但是你无法回避linux平台赋予你微调内核的能力。比如,内核TCP/IP协 议栈使用内存池管理sk_buff结构,那么可以在运行时期动态调整这个内存pool(skb_head_pool)的大小— 通过echo XXXX>/proc/sys/net/core/hot_list_length完成。再比如listen函数的第2个参数(TCP完成3次握手 的数据包队列长度),也可以根据你平台内存大小动态调整。更甚至在一个数据包面数目巨大但同时每个数据包本身大小却很小的特殊系统上尝试最新的NAPI网卡驱动架构。

        (epoll内容,参考epoll_互动百科)

          推荐设置worker的个数为cpu的核数,在这里就很容易理解了,更多的worker数,只会导致进程来竞争cpu资源了,从而带来不必要的上下文切换。而且,nginx为了更好的利用多核特性,提供了cpu亲缘性的绑定选项,我们可以将某一个进程绑定在某一个核上,这样就不会因为进程的切换带来cache的失效。像这种小的优化在nginx中非常常见,同时也说明了nginx作者的苦心孤诣。比如,nginx在做4个字节的字符串比较时,会将4个字符转换成一个int型,再作比较,以减少cpu的指令数等等。

    代码来总结一下nginx的事件处理模型:

     

    while (true) {
        for t in run_tasks:
            t.handler();
        update_time(&now);
        timeout = ETERNITY;
        for t in wait_tasks: /* sorted already */
            if (t.time <= now) {
                t.timeout_handler();
            } else {
                timeout = t.time - now;
                break;
            }
        nevents = poll_function(events, timeout);
        for i in nevents:
            task t;
            if (events[i].type == READ) {
                t.handler = read_handler;
            } else { /* events[i].type == WRITE */
                t.handler = write_handler;
            }
            run_tasks_add(t);
    }

     

     

     

    四.  Nginx+FastCGI运行原理


    1、什么是 FastCGI

    FastCGI是一个可伸缩地、高速地在HTTP server和动态脚本语言间通信的接口。多数流行的HTTP server都支持FastCGI,包括Apache、Nginx和lighttpd等。同时,FastCGI也被许多脚本语言支持,其中就有PHP。

    FastCGI是从CGI发展改进而来的。传统CGI接口方式的主要缺点是性能很差,因为每次HTTP服务器遇到动态程序时都需要重新启动脚本解析器来执行解析,然后将结果返回给HTTP服务器。这在处理高并发访问时几乎是不可用的。另外传统的CGI接口方式安全性也很差,现在已经很少使用了。

    FastCGI接口方式采用C/S结构,可以将HTTP服务器和脚本解析服务器分开,同时在脚本解析服务器上启动一个或者多个脚本解析守护进程。当HTTP服务器每次遇到动态程序时,可以将其直接交付给FastCGI进程来执行,然后将得到的结果返回给浏览器。这种方式可以让HTTP服务器专一地处理静态请求或者将动态脚本服务器的结果返回给客户端,这在很大程度上提高了整个应用系统的性能。

    2、Nginx+FastCGI运行原理

    Nginx不支持对外部程序的直接调用或者解析,所有的外部程序(包括PHP)必须通过FastCGI接口来调用。FastCGI接口在Linux下是socket(这个socket可以是文件socket,也可以是ip socket)。

    wrapper:为了调用CGI程序,还需要一个FastCGI的wrapper(wrapper可以理解为用于启动另一个程序的程序),这个wrapper绑定在某个固定socket上,如端口或者文件socket。当Nginx将CGI请求发送给这个socket的时候,通过FastCGI接口,wrapper接收到请求,然后Fork(派生)出一个新的线程,这个线程调用解释器或者外部程序处理脚本并读取返回数据;接着,wrapper再将返回的数据通过FastCGI接口,沿着固定的socket传递给Nginx;最后,Nginx将返回的数据(html页面或者图片)发送给客户端。这就是Nginx+FastCGI的整个运作过程,如图1-3所示。

           

     

          所以,我们首先需要一个wrapper,这个wrapper需要完成的工作:

    1. 通过调用fastcgi(库)的函数通过socket和ningx通信(读写socket是fastcgi内部实现的功能,对wrapper是非透明的)
    2. 调度thread,进行fork和kill
    3. 和application(php)进行通信

    3、spawn-fcgi与PHP-FPM

           FastCGI接口方式在脚本解析服务器上启动一个或者多个守护进程对动态脚本进行解析,这些进程就是FastCGI进程管理器,或者称为FastCGI引擎。 spawn-fcgi与PHP-FPM就是支持PHP的两个FastCGI进程管理器。因此HTTPServer完全解放出来,可以更好地进行响应和并发处理。

           spawn-fcgi与PHP-FPM的异同:
           1)spawn-fcgi是HTTP服务器lighttpd的一部分,目前已经独立成为一个项目,一般与lighttpd配合使用来支持PHP。但是ligttpd的spwan-fcgi在高并发访问的时候,会出现内存泄漏甚至自动重启FastCGI的问题。即:PHP脚本处理器当机,这个时候如果用户访问的话,可能就会出现白页(即PHP不能被解析或者出错)。

           2)Nginx是个轻量级的HTTP server,必须借助第三方的FastCGI处理器才可以对PHP进行解析,因此其实这样看来nginx是非常灵活的,它可以和任何第三方提供解析的处理器实现连接从而实现对PHP的解析(nginx.conf中很容易设置)nginx也可以使用spwan-fcgi(需要一同安装lighttpd,但是需要为nginx避开端口,一些较早的blog有这方面安装的教程),但是由于spawn-fcgi具有上面所述的用户逐渐发现的缺陷,现在慢慢减少用nginx+spawn-fcgi组合了。

           由于spawn-fcgi的缺陷,现在出现了第三方(目前已经加入到PHP core中)的PHP的FastCGI处理器PHP-FPM,它和spawn-fcgi比较起来有如下优点:

           由于它是作为PHP的patch补丁来开发的,安装的时候需要和php源码一起编译,也就是说编译到php core中了,因此在性能方面要优秀一些;

    同时它在处理高并发方面也优于spawn-fcgi,至少不会自动重启fastcgi处理器。因此,推荐使用Nginx+PHP/PHP-FPM这个组合对PHP进行解析。

          相对Spawn-FCGI,PHP-FPM在CPU和内存方面的控制都更胜一筹,而且前者很容易崩溃,必须用crontab进行监控,而PHP-FPM则没有这种烦恼。
           FastCGI 的主要优点是把动态语言和HTTP Server分离开来,所以Nginx与PHP/PHP-FPM经常被部署在不同的服务器上,以分担前端Nginx服务器的压力,使Nginx专一处理静态请求和转发动态请求,而PHP/PHP-FPM服务器专一解析PHP动态请求。

    4、Nginx+PHP-FPM

          PHP-FPM是管理FastCGI的一个管理器,它作为PHP的插件存在,在安装PHP要想使用PHP-FPM时在老php的老版本(php5.3.3之前)就需要把PHP-FPM以补丁的形式安装到PHP中,而且PHP要与PHP-FPM版本一致,这是必须的)

       PHP-FPM其实是PHP源代码的一个补丁,旨在将FastCGI进程管理整合进PHP包中。必须将它patch到你的PHP源代码中,在编译安装PHP后才可以使用。
       PHP5.3.3已经集成php-fpm了,不再是第三方的包了。PHP-FPM提供了更好的PHP进程管理方式,可以有效控制内存和进程、可以平滑重载PHP配置,比spawn-fcgi具有更多优点,所以被PHP官方收录了。在./configure的时候带 –enable-fpm参数即可开启PHP-FPM。

          fastcgi已经在php5.3.5的core中了,不必在configure时添加 --enable-fastcgi了。老版本如php5.2的需要加此项。

          当我们安装Nginx和PHP-FPM完后,配置信息:

         PHP-FPM的默认配置php-fpm.conf:

         listen_address  127.0.0.1:9000 #这个表示php的fastcgi进程监听的ip地址以及端口

          start_servers

          min_spare_servers

          max_spare_servers

          Nginx配置运行php: 编辑nginx.conf加入如下语句:

          location ~ \.php$ {
                root html;   
                fastcgi_pass 127.0.0.1:9000; 指定了fastcgi进程侦听的端口,nginx就是通过这里与php交互的
                fastcgi_index index.php;
                include fastcgi_params;
                 fastcgi_param SCRIPT_FILENAME   /usr/local/nginx/html$fastcgi_script_name;
        }

        Nginx通过location指令,将所有以php为后缀的文件都交给127.0.0.1:9000来处理,而这里的IP地址和端口就是FastCGI进程监听的IP地址和端口。

         其整体工作流程:

         1)、FastCGI进程管理器php-fpm自身初始化,启动主进程php-fpm和启动start_servers个CGI 子进程。

               主进程php-fpm主要是管理fastcgi子进程,监听9000端口。

               fastcgi子进程等待来自Web Server的连接。

         2)、当客户端请求到达Web Server Nginx是时,Nginx通过location指令,将所有以php为后缀的文件都交给127.0.0.1:9000来处理,即Nginx通过location指令,将所有以php为后缀的文件都交给127.0.0.1:9000来处理。

          3)FastCGI进程管理器PHP-FPM选择并连接到一个子进程CGI解释器。Web server将CGI环境变量和标准输入发送到FastCGI子进程。

          4)、FastCGI子进程完成处理后将标准输出和错误信息从同一连接返回Web Server。当FastCGI子进程关闭连接时,请求便告处理完成。

          5)、FastCGI子进程接着等待并处理来自FastCGI进程管理器(运行在 WebServer中)的下一个连接。

     

    五.   Nginx+PHP正确配置


    一般web都做统一入口:把PHP请求都发送到同一个文件上,然后在此文件里通过解析「REQUEST_URI」实现路由。

    Nginx配置文件分为好多块,常见的从外到内依次是「http」、「server」、「location」等等,缺省的继承关系是从外到内,也就是说内层块会自动获取外层块的值作为缺省值。

    例如:

    server {
        listen 80;
        server_name foo.com;
        root /path;
        location / {
            index index.html index.htm index.php;
            if (!-e $request_filename) {
                rewrite . /index.php last;
            }
        }
        location ~ \.php$ {
            include fastcgi_params;
            fastcgi_param SCRIPT_FILENAME /path$fastcgi_script_name;
            fastcgi_pass 127.0.0.1:9000;
            fastcgi_index index.php;
        }
    } 

    1)  不应该在location 模块定义index

    一旦未来需要加入新的「location」,必然会出现重复定义的「index」指令,这是因为多个「location」是平级的关系,不存在继承,此时应该在「server」里定义「index」,借助继承关系,「index」指令在所有的「location」中都能生效。

    2)     使用try_files

    接下来看看「if」指令,说它是大家误解最深的Nginx指令毫不为过:

    if (!-e $request_filename) {
        rewrite . /index.php last;
    }

    很多人喜欢用「if」指令做一系列的检查,不过这实际上是「try_files」指令的职责:

    try_files $uri $uri/ /index.php;

    除此以外,初学者往往会认为「if」指令是内核级的指令,但是实际上它是rewrite模块的一部分,加上Nginx配置实际上是声明式的,而非过程式的,所以当其和非rewrite模块的指令混用时,结果可能会非你所愿。

     

    3)fastcgi_params」配置文件

    include fastcgi_params;

    Nginx有两份fastcgi配置文件,分别是「fastcgi_params」和「fastcgi.conf」,它们没有太大的差异,唯一的区别是后者比前者多了一行「SCRIPT_FILENAME」的定义:

    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;

    注意:$document_root 和 $fastcgi_script_name 之间没有 /。

    原本Nginx只有「fastcgi_params」,后来发现很多人在定义「SCRIPT_FILENAME」时使用了硬编码的方式,于是为了规范用法便引入了「fastcgi.conf」。

     

    不过这样的话就产生一个疑问:为什么一定要引入一个新的配置文件,而不是修改旧的配置文件?这是因为「fastcgi_param」指令是数组型的,和普通指令相同的是:内层替换外层;和普通指令不同的是:当在同级多次使用的时候,是新增而不是替换。换句话说,如果在同级定义两次「SCRIPT_FILENAME」,那么它们都会被发送到后端,这可能会导致一些潜在的问题,为了避免此类情况,便引入了一个新的配置文件。

    此外,我们还需要考虑一个安全问题:在PHP开启「cgi.fix_pathinfo」的情况下,PHP可能会把错误的文件类型当作PHP文件来解析。如果Nginx和PHP安装在同一台服务器上的话,那么最简单的解决方法是用「try_files」指令做一次过滤:

     

    try_files $uri =404;

    依照前面的分析,给出一份改良后的版本,是不是比开始的版本清爽了很多:

    server {
        listen 80;
        server_name foo.com;
        root /path;
        index index.html index.htm index.php;
        location / {
            try_files $uri $uri/ /index.php;
        }
        location ~ \.php$ {
           try_files $uri =404;
           include fastcgi.conf;
           fastcgi_pass 127.0.0.1:9000;
       }
    }

     

     

     

     

    六.   Nginx优化


     1. 编译安装过程优化

    1).减小Nginx编译后的文件大小

    在编译Nginx时,默认以debug模式进行,而在debug模式下会插入很多跟踪和ASSERT之类的信息,编译完成后,一个Nginx要有好几兆字节。而在编译前取消Nginx的debug模式,编译完成后Nginx只有几百千字节。因此可以在编译之前,修改相关源码,取消debug模式。具体方法如下:

    在Nginx源码文件被解压后,找到源码目录下的auto/cc/gcc文件,在其中找到如下几行:

     # debug  
    CFLAGS=”$CFLAGS -g” 

    注释掉或删掉这两行,即可取消debug模式。

    2.为特定的CPU指定CPU类型编译优化

    在编译Nginx时,默认的GCC编译参数是“-O”,要优化GCC编译,可以使用以下两个参数:

    1. --with-cc-opt='-O3' 
    2. --with-cpu-opt=CPU  #为特定的 CPU 编译,有效的值包括:
      pentium, pentiumpro, pentium3, # pentium4, athlon, opteron, amd64, sparc32, sparc64, ppc64 

    要确定CPU类型,可以通过如下命令: #cat /proc/cpuinfo | grep "model name" 

     

    2. 利用TCMalloc优化Nginx的性能

    TCMalloc的全称为Thread-Caching Malloc,是谷歌开发的开源工具google-perftools中的一个成员。与标准的glibc库的Malloc相比,TCMalloc库在内存分配效率和速度上要高很多,这在很大程度上提高了服务器在高并发情况下的性能,从而降低了系统的负载。下面简单介绍如何为Nginx添加TCMalloc库支持。

    要安装TCMalloc库,需要安装libunwind(32位操作系统不需要安装)和google-perftools两个软件包,libunwind库为基于64位CPU和操作系统的程序提供了基本函数调用链和函数调用寄存器功能。下面介绍利用TCMalloc优化Nginx的具体操作过程。

    1).安装libunwind库

    可以从http://download.savannah.gnu.org/releases/libunwind下载相应的libunwind版本,这里下载的是libunwind-0.99-alpha.tar.gz。安装过程如下

    #tar zxvf libunwind-0.99-alpha.tar.gz  
    # cd libunwind-0.99-alpha/  
    #CFLAGS=-fPIC ./configure  
    #make CFLAGS=-fPIC  
    #make CFLAGS=-fPIC install

    2).安装google-perftools

    可以从http://google-perftools.googlecode.com下载相应的google-perftools版本,这里下载的是google-perftools-1.8.tar.gz。安装过程如下:

    [root@localhost home]#tar zxvf google-perftools-1.8.tar.gz  
    [root@localhost home]#cd google-perftools-1.8/  
    [root@localhost google-perftools-1.8]# ./configure  
    [root@localhost google-perftools-1.8]#make && make install  
    [root@localhost google-perftools-1.8]#echo "/usr/
    local/lib" > /etc/ld.so.conf.d/usr_local_lib.conf  
    [root@localhost google-perftools-1.8]# ldconfig

    至此,google-perftools安装完成。

    3).重新编译Nginx

    为了使Nginx支持google-perftools,需要在安装过程中添加“–with-google_perftools_module”选项重新编译Nginx。安装代码如下:

    [root@localhostnginx-0.7.65]#./configure \  
    >--with-google_perftools_module --with-http_stub_status_module  --prefix=/opt/nginx  
    [root@localhost nginx-0.7.65]#make  
    [root@localhost nginx-0.7.65]#make install

    到这里Nginx安装完成。

    4).为google-perftools添加线程目录

    创建一个线程目录,这里将文件放在/tmp/tcmalloc下。操作如下:

    [root@localhost home]#mkdir /tmp/tcmalloc  
    [root@localhost home]#chmod 0777 /tmp/tcmalloc

    5).修改Nginx主配置文件

    修改nginx.conf文件,在pid这行的下面添加如下代码:

    #pid        logs/nginx.pid;  
    google_perftools_profiles /tmp/tcmalloc;

    接着,重启Nginx即可完成google-perftools的加载。

    6).验证运行状态

    为了验证google-perftools已经正常加载,可通过如下命令查看:

    [root@ localhost home]# lsof -n | grep tcmalloc  
    nginx      2395 nobody   9w  REG    8,8       0    1599440 /tmp/tcmalloc.2395  
    nginx      2396 nobody   11w REG   8,8       0    1599443 /tmp/tcmalloc.2396  
    nginx      2397 nobody   13w REG  8,8        0    1599441  /tmp/tcmalloc.2397  
    nginx     2398 nobody    15w REG  8,8     0    1599442 /tmp/tcmalloc.2398

    由于在Nginx配置文件中设置worker_processes的值为4,因此开启了4个Nginx线程,每个线程会有一行记录。每个线程文件后面的数字值就是启动的Nginx的pid值。

    至此,利用TCMalloc优化Nginx的操作完成。

    3.Nginx内核参数优化

    内核参数的优化,主要是在Linux系统中针对Nginx应用而进行的系统内核参数优化。

    下面给出一个优化实例以供参考。

    net.ipv4.tcp_max_tw_buckets = 6000
    net.ipv4.ip_local_port_range = 1024 65000  
    net.ipv4.tcp_tw_recycle = 1
    net.ipv4.tcp_tw_reuse = 1
    net.ipv4.tcp_syncookies = 1
    net.core.somaxconn = 262144
    net.core.netdev_max_backlog = 262144
    net.ipv4.tcp_max_orphans = 262144
    net.ipv4.tcp_max_syn_backlog = 262144
    net.ipv4.tcp_synack_retries = 1
    net.ipv4.tcp_syn_retries = 1
    net.ipv4.tcp_fin_timeout = 1
    net.ipv4.tcp_keepalive_time = 30

     

    将上面的内核参数值加入/etc/sysctl.conf文件中,然后执行如下命令使之生效:

    [root@ localhost home]#/sbin/sysctl -p 

    下面对实例中选项的含义进行介绍:

    TCP参数设置:

    net.ipv4.tcp_max_tw_buckets :选项用来设定timewait的数量,默认是180 000,这里设为6000。

    net.ipv4.ip_local_port_range:选项用来设定允许系统打开的端口范围。在高并发情况否则端口号会不够用。当NGINX充当代理时,每个到上游服务器的连接都使用一个短暂或临时端口。

    net.ipv4.tcp_tw_recycle:选项用于设置启用timewait快速回收.

    net.ipv4.tcp_tw_reuse:选项用于设置开启重用,允许将TIME-WAIT sockets重新用于新的TCP连接。

    net.ipv4.tcp_syncookies:选项用于设置开启SYN Cookies,当出现SYN等待队列溢出时,启用cookies进行处理。

    net.ipv4.tcp_max_orphans:选项用于设定系统中最多有多少个TCP套接字不被关联到任何一个用户文件句柄上。如果超过这个数字,孤立连接将立即被复位并打印出警告信息。这个限制只是为了防止简单的DoS攻击。不能过分依靠这个限制甚至人为减小这个值,更多的情况下应该增加这个值。

    net.ipv4.tcp_max_syn_backlog:选项用于记录那些尚未收到客户端确认信息的连接请求的最大值。对于有128MB内存的系统而言,此参数的默认值是1024,对小内存的系统则是128。

    net.ipv4.tcp_synack_retries参数的值决定了内核放弃连接之前发送SYN+ACK包的数量。

    net.ipv4.tcp_syn_retries选项表示在内核放弃建立连接之前发送SYN包的数量。

    net.ipv4.tcp_fin_timeout选项决定了套接字保持在FIN-WAIT-2状态的时间。默认值是60秒。正确设置这个值非常重要,有时即使一个负载很小的Web服务器,也会出现大量的死套接字而产生内存溢出的风险。

    net.ipv4.tcp_syn_retries选项表示在内核放弃建立连接之前发送SYN包的数量。

    如果发送端要求关闭套接字,net.ipv4.tcp_fin_timeout选项决定了套接字保持在FIN-WAIT-2状态的时间。接收端可以出错并永远不关闭连接,甚至意外宕机。

    net.ipv4.tcp_fin_timeout的默认值是60秒。需要注意的是,即使一个负载很小的Web服务器,也会出现因为大量的死套接字而产生内存溢出的风险。FIN-WAIT-2的危险性比FIN-WAIT-1要小,因为它最多只能消耗1.5KB的内存,但是其生存期长些。

    net.ipv4.tcp_keepalive_time选项表示当keepalive启用的时候,TCP发送keepalive消息的频度。默认值是2(单位是小时)。

     

    缓冲区队列:

    net.core.somaxconn:选项的默认值是128, 这个参数用于调节系统同时发起的tcp连接数,在高并发的请求中,默认的值可能会导致链接超时或者重传,因此,需要结合并发请求数来调节此值。

     由NGINX可接受的数目决定。默认值通常很低,但可以接受,因为NGINX 接收连接非常快,但如果网站流量大时,就应该增加这个值。内核日志中的错误消息会提醒这个值太小了,把值改大,直到错误提示消失。
    注意: 如果设置这个值大于512,相应地也要改变NGINX listen指令的backlog参数。

    net.core.netdev_max_backlog:选项表示当每个网络接口接收数据包的速率比内核处理这些包的速率快时,允许发送到队列的数据包的最大数目。

     

    4.PHP-FPM的优化

    如果您高负载网站使用PHP-FPM管理FastCGI,这些技巧也许对您有用:

    1)增加FastCGI进程数

    把PHP FastCGI子进程数调到100或以上,在4G内存的服务器上200就可以建议通过压力测试获取最佳值。

    2)增加 PHP-FPM打开文件描述符的限制

    标签rlimit_files用于设置PHP-FPM对打开文件描述符的限制,默认值为1024。这个标签的值必须和Linux内核打开文件数关联起来,例如,要将此值设置为65 535,就必须在Linux命令行执行“ulimit -HSn 65536”。

           然后 增加 PHP-FPM打开文件描述符的限制:
         # vi /path/to/php-fpm.conf
        找到
    “<valuename="rlimit_files">1024</value>”
    把1024更改为 4096或者更高
    .
    重启 PHP-FPM.

           ulimit -n 要调整为65536甚至更大。如何调这个参数,可以参考网上的一些文章。命令行下执行 ulimit -n 65536即可修改。如果不能修改,需要设置  /etc/security/limits.conf,加入

    * hard nofile65536

    * soft nofile 65536

             3)适当增加max_requests

        标签max_requests指明了每个children最多处理多少个请求后便会被关闭,默认的设置是500。

        <value name="max_requests"> 500 </value>

    5.nginx.conf的参数优化

    nginx要开启的进程数 一般等于cpu的总核数 其实一般情况下开4个或8个就可以。

        每个nginx进程消耗的内存10兆的模样

    worker_cpu_affinity
        仅适用于linux,使用该选项可以绑定worker进程和CPU(2.4内核的机器用不了)

        假如是8 cpu 分配如下:
        worker_cpu_affinity 00000001 00000010 00000100 00001000 00010000

    00100000 01000000 10000000

        nginx可以使用多个worker进程,原因如下:

    to use SMP 
    to decrease latency when workers blockend on disk I/O 
    to limit number of connections per process when select()/poll() is

    used The worker_processes and worker_connections from the event sections

    allows you to calculate maxclients value: k max_clients = worker_processes * worker_connections

    worker_rlimit_nofile 102400;

        每个nginx进程打开文件描述符最大数目 配置要和系统的单进程打开文件数一致,linux 2.6内核下开启文件打开数为65535,worker_rlimit_nofile就相应应该填写65535 nginx调度时分配请求到进程并不是那么的均衡,假如超过会返回502错误。我这里写的大一点

    use epoll

        Nginx使用了最新的epoll(Linux 2.6内核)和kqueue(freebsd)网络I/O模型,而Apache则使用的是传统的select模型。

    处理大量的连接的读写,Apache所采用的select网络I/O模型非常低效。在高并发服务器中,轮询I/O是最耗时间的操作 目前Linux下能够承受高并发

        访问的Squid、Memcached都采用的是epoll网络I/O模型。

    worker_processes 

        NGINX工作进程数(默认值是1)。在大多数情况下,一个CPU内核运行一个工作进程最好,建议将这个指令设置成自动就可以。有时可能想增大这个值,比如当工作进程需要做大量的磁盘I/O。

    worker_connections 65535;
       每个工作进程允许最大的同时连接数 (Maxclient = work_processes * worker_connections)

    keepalive_timeout 75

        keepalive超时时间

        这里需要注意官方的一句话:
    The parameters can differ from each other. Line Keep-Alive:

    timeout=time understands Mozilla and Konqueror. MSIE itself shuts

    keep-alive connection approximately after 60 seconds.

     

    client_header_buffer_size 16k
    large_client_header_buffers 4 32k

    客户请求头缓冲大小 
    nginx默认会用client_header_buffer_size这个buffer来读取header值,如果header过大,它会使用large_client_header_buffers来读取

    如果设置过小HTTP头/Cookie过大 会报400 错误 nginx 400 bad request
    求行如果超过buffer,就会报HTTP 414错误(URI Too Long) nginx接受最长的HTTP头部大小必须比其中一个buffer大,否则就会报400的HTTP错误(Bad Request)。

    open_file_cache max 102400

    使用字段:http, server, location 这个指令指定缓存是否启用,如果启用,将记录文件以下信息: ·打开的文件描述符,大小信息和修改时间. ·存在的目录信息. ·在搜索文件过程中的错误信息 -- 没有这个文件,无法正确读取,参考open_file_cache_errors 指令选项:
    ·max - 指定缓存的最大数目,如果缓存溢出,最长使用过的文件(LRU)将被移除
    例: open_file_cache max=1000 inactive=20s; open_file_cache_valid 30s; open_file_cache_min_uses 2; open_file_cache_errors on;

    open_file_cache_errors
    语法:open_file_cache_errors on | off 默认值:open_file_cache_errors off 使用字段:http, server, location 这个指令指定是否在搜索一个文件是记录cache错误.

    open_file_cache_min_uses

    语法:open_file_cache_min_uses number 默认值:open_file_cache_min_uses 1 使用字段:http, server, location 这个指令指定了在open_file_cache指令无效的参数中一定的时间范围内可以使用的最小文件数,如 果使用更大的值,文件描述符在cache中总是打开状态.
    open_file_cache_valid

    语法:open_file_cache_valid time 默认值:open_file_cache_valid 60 使用字段:http, server, location 这个指令指定了何时需要检查open_file_cache中缓存项目的有效信息.


    开启gzip
    gzip on;
    gzip_min_length 1k;
    gzip_buffers 4 16k;
    gzip_http_version 1.0;
    gzip_comp_level 2;
    gzip_types text/plain application/x-javascript text/css

    application/xml;
    gzip_vary on;

    缓存静态文件:

    location ~* ^.+\.(swf|gif|png|jpg|js|css)$ {
        root /usr/local/ku6/ktv/show.ku6.com/;
        expires 1m;
    }

     

    响应缓冲区:

    比如我们Nginx+Tomcat 代理访问JS无法完全加载,这几个参数影响:

    proxy_buffer_size 128k;
    proxy_buffers   32 128k;
    proxy_busy_buffers_size 128k;

    Nginx在代理了相应服务后或根据我们配置的UpStream和location来获取相应的文件,首先文件会被解析到nginx的内存或者临时文件目录中,然后由nginx再来响应。那么当proxy_buffers和proxy_buffer_size以及proxy_busy_buffers_size 都太小时,会将内容根据nginx的配置生成到临时文件中,但是临时文件的大小也有默认值。所以当这四个值都过小时就会导致部分文件只加载一部分。所以要根据我们的服务器情况适当的调整proxy_buffers和proxy_buffer_size以及proxy_busy_buffers_size、proxy_temp_file_write_size。具体几个参数的详细如下:

    proxy_buffers   32 128k;  设置了32个缓存区,每个的大小是128k

    proxy_buffer_size 128k; 每个缓存区的大小是128k,当两个值不一致时没有找到具体哪个有效,建议和上面的设置一致。

    proxy_busy_buffers_size 128k;设置使用中的缓存区的大小,控制传输至客户端的缓存的最大

    proxy_temp_file_write_size 缓存文件的大小

     

    6.优化访问日志

        记录每个请求会消耗CPU和I/O周期,一种降低这种影响的方式是缓冲访问日志。使用缓冲,而不是每条日志记录都单独执行写操作,NGINX会缓冲一连串的日志记录,使用单个操作把它们一起写到文件中。
        要启用访问日志的缓存,就涉及到在access_log指令中buffer=size这个参数。当缓冲区达到size值时,NGINX会把缓冲区的内容写到日志中。让NGINX在指定的一段时间后写缓存,就包含flush=time参数。当两个参数都设置了,当下个日志条目超出缓冲区值或者缓冲区中日志条目存留时间超过设定的时间值,NGINX都会将条目写入日志文件。当工作进程重新打开它的日志文件或退出时,也会记录下来。要完全禁用访问日志记录的功能,将access_log 指令设置成off参数。

     

    7.限流

    你可以设置多个限制,防止用户消耗太多的资源,避免影响系统性能和用户体验及安全。 以下是相关的指令:
    limit_conn and limit_conn_zone:NGINX接受客户连接的数量限制,例如单个IP地址的连接。设置这些指令可以防止单个用户打开太多的连接,消耗超出自己的资源。
    limit_rate:传输到客户端响应速度的限制(每个打开多个连接的客户消耗更多的带宽)。设置这个限制防止系统过载,确保所有客户端更均匀的服务质量。
    limit_req and limit_req_zone:NGINX处理请求的速度限制,与limit_rate有相同的功能。可以提高安全性,尤其是对登录页面,通过对用户限制请求速率设置一个合理的值,避免太慢的程序覆盖你的应用请求(比如DDoS攻击)。
    max_conns:上游配置块中服务器指令参数。在上游服务器组中单个服务器可接受最大并发数量。使用这个限制防止上游服务器过载。设置值为0(默认值)表示没有限制。
    queue (NGINX Plus) :创建一个队列,用来存放在上游服务器中超出他们最大max_cons限制数量的请求。这个指令可以设置队列请求的最大值,还可以选择设置在错误返回之前最大等待时间(默认值是60秒)。如果忽略这个指令,请求不会放入队列。

     

     

     

     

    七.   错误排查


    所有错误的都在错误文件error.log里面

    1、400 bad request错误的原因和解决办法

         配置nginx.conf相关设置如下.

         client_header_buffer_size 16k;
         large_client_header_buffers 4 64k;

         根据具体情况调整,一般适当调整值就可以。

    2、Nginx出现的413 Request Entity Too Large错误

             这个错误一般在上传文件的时候会出现,

            client intended to send too large body: 23937077 bytes, client: 10.45.142.41, server: localhost, request

            服务器拒绝处理当前请求,因为该请求提交的实体数据大小超过了服务器愿意或者能够处理的范围。此种情况下,服务器可以关闭连接以免客户端继续发送此请求。
      如果这个状况是临时的,服务器应当返回一个 Retry-After 的响应头,以告知客户端可以在多少时间以后重新尝试。

             编辑Nginx主配置文件Nginx.conf,找到http{}段,添加

            client_max_body_size 10m; //设置多大根据自己的需求作调整.

            如果运行php的话这个大小client_max_body_size要和php.ini中的如下值的最大值一致或者稍大,这样就不会因为提交数据大         小不一致出现的错误。

            post_max_size = 10M
            upload_max_filesize = 2M

     

    1、Nginx 502 Bad Gateway:

    作为网关或者代理工作的服务器尝试执行请求时,从上游服务器接收到无效的响应。

    常见原因:
    1、后端服务挂了的情况,直接502 (nginx error日志:connect() failed (111: Connection refused) )
    2、后端服务在重启
    实例:将后端服务关掉,然后向nginx发送请求后端接口,nginx日志可以看到502错误。

    如果nginx+php出现502, 错误分析:

    php-cgi进程数不够用、php执行时间长(mysql慢)、或者是php-cgi进程死掉,都会出现502错误

    一般来说Nginx 502 Bad Gateway和php-fpm.conf的设置有关,而Nginx 504 Gateway Time-out则是与nginx.conf的设置有关

    1)、查看当前的PHP FastCGI进程数是否够用:

    netstat -anpo | grep "php-cgi" | wc -l

      如果实际使用的“FastCGI进程数”接近预设的“FastCGI进程数”,那么,说明“FastCGI进程数”不够用,需要增大。

    2)、部分PHP程序的执行时间超过了Nginx的等待时间,可以适当增加

         nginx.conf配置文件中FastCGI的timeout时间,例如:

    http {
        ......
        fastcgi_connect_timeout 300;
        fastcgi_send_timeout 300;
        fastcgi_read_timeout 300;
        ......
    }

    2、504 Gateway Timeout :

    nginx作为网关或者代理工作的服务器尝试执行请求时,未能及时从上游服务器(URI标识出的服务器,例如HTTP、FTP、LDAP)收到响应。

    常见原因:
    该接口太耗时,后端服务接收到请求,开始执行,未能在设定时间返回数据给nginx
    后端服务器整体负载太高,接受到请求之后,由于线程繁忙,未能安排给请求的接口,导致未能在设定时间返回数据给nginx

     

    2、413 Request Entity Too Large
       

        解决:增大client_max_body_size

        client_max_body_size:指令指定允许客户端连接的最大请求实体大小,它出现在请求头部的Content-Length字段. 如果请求大于指定的值,客户端将收到一个"Request Entity Too Large" (413)错误. 记住,浏览器并不知道怎样显示这个错误.

        php.ini中增大
    post_max_size 和upload_max_filesize

    3、 Ngnix error.log出现:upstream sent too big header while reading response header from upstream错误

    1)如果是nginx反向代理
       proxy是nginx作为client转发时使用的,如果header过大,超出了默认的1k,就会引发上述的upstream sent too big header (说白了就是nginx把外部请求给后端server,后端server返回的header  太大nginx处理不过来就导致了。

      server {
            listen       80;
            server_name  *.xywy.com ;

            large_client_header_buffers 4 16k;

            location / {

              #添加这3行 
               proxy_buffer_size 64k;
               proxy_buffers   32 32k;
               proxy_busy_buffers_size 128k;

               proxy_set_header Host $host;
               proxy_set_header X-Real-IP       $remote_addr;
               proxy_set_header X-Forwarded-For  $proxy_add_x_forwarded_for;

        }

    }        

    2) 如果是 nginx+PHPcgi 

      错误带有 upstream: "fastcgi://127.0.0.1:9000"。就该 

      多加:

      fastcgi_buffer_size 128k;
      fastcgi_buffers 4 128k;

    server {
            listen       80;
            server_name  ddd.com;
            index index.html index.htm index.php;
       
            client_header_buffer_size 128k;
            large_client_header_buffers 4 128k;
            proxy_buffer_size 64k;
            proxy_buffers 8 64k;
            fastcgi_buffer_size 128k;
            fastcgi_buffers 4 128k;

            location / {

              ......

            }

    }         

     

    八  、 Nginx的php漏洞


    漏洞介绍:nginx是一款高性能的web服务器,使用非常广泛,其不仅经常被用作反向代理,也可以非常好的支持PHP的运行。80sec发现其中存在一个较为严重的安全问题,默认情况下可能导致服务器错误的将任何类型的文件以PHP的方式进行解析,这将导致严重的安全问题,使得恶意的攻击者可能攻陷支持php的nginx服务器。
    漏洞分析:nginx默认以cgi的方式支持php的运行,譬如在配置文件当中可以以
    location ~ .php$ {
    root html;
    fastcgi_pass 127.0.0.1:9000;
    fastcgi_index index.php;
    fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
    include fastcgi_params;
    }


    的方式支持对php的解析,location对请求进行选择的时候会使用URI环境变量进行选择,其中传递到后端Fastcgi的关键变量SCRIPT_FILENAME由nginx生成的$fastcgi_script_name决定,而通过分析可以看到$fastcgi_script_name是直接由URI环境变量控制的,这里就是产生问题的点。而为了较好的支持PATH_INFO的提取,在PHP的配置选项里存在cgi.fix_pathinfo选项,其目的是为了从SCRIPT_FILENAME里取出真正的脚本名。
    那么假设存在一个http://www.80sec.com/80sec.jpg,我们以如下的方式去访问

    http://www.80sec.com/80sec.jpg/80sec.php
    将会得到一个URI
    /80sec.jpg/80sec.php

    经过location指令,该请求将会交给后端的fastcgi处理,nginx为其设置环境变量SCRIPT_FILENAME,内容为
    /scripts/80sec.jpg/80sec.php
    而在其他的webserver如lighttpd当中,我们发现其中的SCRIPT_FILENAME被正确的设置为
    /scripts/80sec.jpg
    所以不存在此问题。
    后端的fastcgi在接受到该选项时,会根据fix_pathinfo配置决定是否对SCRIPT_FILENAME进行额外的处理,一般情况下如果不对fix_pathinfo进行设置将影响使用PATH_INFO进行路由选择的应用,所以该选项一般配置开启。Php通过该选项之后将查找其中真正的脚本文件名字,查找的方式也是查看文件是否存在,这个时候将分离出SCRIPT_FILENAME和PATH_INFO分别为
    /scripts/80sec.jpg和80sec.php
    最后,以/scripts/80sec.jpg作为此次请求需要执行的脚本,攻击者就可以实现让nginx以php来解析任何类型的文件了。

    POC: 访问一个nginx来支持php的站点,在一个任何资源的文件如robots.txt后面加上/80sec.php,这个时候你可以看到如下的区别:

    访问http://www.80sec.com/robots.txt

    HTTP/1.1 200 OK
    Server: nginx/0.6.32
    Date: Thu, 20 May 2010 10:05:30 GMT
    Content-Type: text/plain
    Content-Length: 18
    Last-Modified: Thu, 20 May 2010 06:26:34 GMT
    Connection: keep-alive
    Keep-Alive: timeout=20
    Accept-Ranges: bytes


    访问访问http://www.80sec.com/robots.txt/80sec.php

    HTTP/1.1 200 OK
    Server: nginx/0.6.32
    Date: Thu, 20 May 2010 10:06:49 GMT
    Content-Type: text/html
    Transfer-Encoding: chunked
    Connection: keep-alive
    Keep-Alive: timeout=20
    X-Powered-By: PHP/5.2.6


    其中的Content-Type的变化说明了后端负责解析的变化,该站点就可能存在漏洞。

    漏洞厂商:http://www.nginx.org

    解决方案:

    我们已经尝试联系官方,但是此前你可以通过以下的方式来减少损失

    关闭cgi.fix_pathinfo为0

    或者

    if ( $fastcgi_script_name ~ ..*/.*php ) {
    return 403;
    }

    PS: 鸣谢laruence大牛在分析过程中给的帮助

     

    展开全文
  • 表:flow_form_type 工作流表单表 名称 类型 说明 FORM_ID Int 表单编号 FORM_NAME varchar(200) 表单名称 PRINT_MODEL
  • 问卷星全自动填写脚本【详细使用教程】

    万次阅读 多人点赞 2020-05-15 08:38:40
    插件将会驱使网页,在加载完成自动填写答案,并滚动到提交按钮处,帮你点击它。提交完成后也会帮你自动刷新网页,以便快速进行下一轮作答 首先打开插件(后面的动态图只会播放一遍,如果不动了刷新
  • 工作流引擎

    万次阅读 2015-07-24 14:24:53
    它的主要特点是使处理过程自动化,通过将工作分解成定义良好的任务、角色,按照一定的规则和过程来执行这些任务并对他们进行监控,使人以及各种应用工具相互之间协调工作,以完成某项工作,达到提高办公效率、降低...
  • Activiti工作流教程

    万次阅读 多人点赞 2017-03-03 17:43:32
    1:工作流的概念说明:1) 假设:这两张图就是华谊兄弟的请假流程图2) 图的组成部分:A. 人物:范冰冰 冯小刚王中军B. 事件(动作):请假、批准、不批准 工作流(Workflow),就是“业务过程的部分或整体在计算机...
  • 联合国工作机会,面向中国!!!

    千次阅读 2006-02-14 09:19:00
    联合国工作机会,招中国的原文URL:http://www.un.org/chinese/employment.htm原文内容:
  • 工作季来了,互联网大潮也将至。近来陆续有不少师弟师妹校友求分享各种资料和经验,想来手头上确实还有一些资源,也包括当初博主的师兄师姐们以及某些无私的大牛们分享的资料,笔经面经以及感想。于是翻出来看看,...
  • 如何做好开发组长工作

    万次阅读 2016-05-27 10:06:54
    首先,对上一级,需要通过开发组长或者项目组组长了解整个项目的进度情况以及小组各成员的工作情况,很多时候上一级都会比较忙,而他要了解底下的人员做得怎么样,大多数时候都得靠小组提供这些信息。另有什么新的...
  • 数据工作就组成结构和流程来说还是比较简单的,因为这个工作本来就很年轻,分工还没有很细。总体来讲,我把数据工作看成相互连接的三部分:取数、理数、用数,这是一个闭环。用数的需求会驱动取数工作,并对取数工作...
  • 工作流任务调度系统:Apache DolphinScheduler

    万次阅读 多人点赞 2019-10-28 16:21:51
    Apache DolphinScheduler(目前处在孵化阶段,原名为EasyScheduler)是一个分布式、去中心化、易扩展的可视化DAG工作流任务调度系统,其致力于解决数据处理流程中错综复杂的依赖关系,使调度系统在数据处理流程中开...
  • activity工作流表结构分析

    万次阅读 2016-08-02 10:52:28
    1、结构设计 1.1、 逻辑结构设计 Activiti使用到的表都是ACT_开头的。 ACT_RE_*: ’RE’表示repository(存储),RepositoryService接口所操作的表。...‘RU’表示runtime,运行表-RuntimeService
  • EOS工作流引擎工作原理

    千次阅读 2013-03-14 01:23:20
    EOS工作流引擎工作原理  2011-08-02 23:56:05| 分类: 工作流 | 标签: |字号大中小 订阅 转载于: http://lanhy2000.blog.163.com/blog/static/43678608201172115428592/ 1. 工作流基础知识 ...
  • DHCP工作原理详解

    万次阅读 2014-11-11 17:55:52
    DHCP动态主机配置协议的作用我想作为网管的兄弟们都应该知道了,这里我就不多废话了,今天我要谈的是DHCP的工作过程,了解了工作过程,要排除故障就容易了。   一、DHCP客户机初始化: 1. 寻找DHCP Server。 当...
  • 目录 1.Nginx介绍 1.1 什么是Nginx? 1.2Nginx能做什么 1.3 为什么要选择用Nginx 2.Nginx的安装与配置 2.1 Nginx 安装 ...3.Nginx工作原理 3.1 工作原理: 3.1.1 Nginx处理Request请求过程解析 ...
  • 工作流引擎实现工作管理项目(Shark+JaWE+Tomcat)

    万次阅读 热门讨论 2005-12-05 21:32:00
     我们以一个简单的请假流程来介绍如何实现一个工作流项目: 流程很简单,程序员向部门经理提交一张请假单,部门经理收到后,根据主观意识决定是否批准(黑暗吧,哈哈…) 既然要填写一张请假单,因此我们必须知道”...
  • 工作流的大致开发流程

    千次阅读 2019-05-05 17:25:40
    前段时间公司在做一个oa的项目,用到了flowable工作流,刚开始的时候还在纠结于是用activity还是flowable,后来查了相关资料发现flowable的作者之前就是开发activity的作者,只不过后来自己出去又搞了一套就叫做...
  • LCD工作原理

    万次阅读 2012-02-14 15:35:28
    其实,液晶是一种介于固态和液态之间的物质,当被加热,它会呈现透明的液态,而冷却的时候又会结晶成混乱的固态,液晶是具有规则性分子排列的有机化合物。液晶按照分子结构排列的不同分为三种:类似粘土状的...
  • 在日常工作中常常需要重复填写某些表单,如果人工完成,费时费力,而且网络延迟令人十分崩溃。如果能够用程序实现自动填表,效率可以提高一倍以上,并且能够移植到多台计算机,进一步提高工作效率。webdriver是...
  • 毕业工作五年的总结和感悟(上)

    万次阅读 多人点赞 2016-04-04 20:32:07
    时间过得太快,马上就要大学本科毕业五年了,从实习就...上次写工作总结的时候还是4年前了吧,刚毕业那个时候由于时间全部用于工作和研究技术了,所以可以抽出很多时间来写东西。 其实写文章或者博客是很花时间的,
  • 对于企业应用系统来说,工作流可以说是其核心和灵魂,而审批流程则是比较重要的基础应用场景,一个良好的审批设计可以有效的提高公司运转效率,提升管理规范。 接下来,我们从角色、内容、流程、动作、权限、配置、...
  • java 工作流 详解

    万次阅读 2017-12-17 20:57:31
    工作流基本概念: 什么是工作流?   工作流:两个或两个以上的人,为了共同的目标,连续的以串行或并行的方式去完成某一业务。   业务:工作流所指业务涵盖了与经营相关的活动。    串行或...
  • CF卡工作原理

    千次阅读 2006-05-10 10:50:00
    http://www.beareyes.com.cn/2/lib/200402/11/20040211130.htm(关键字:...的确,虽然形状不同,但CF卡和闪盘结构相似,有许多相同之处,不过它们也有很大的区别,比如二者的管理和工作方式。 现在的CF卡和闪盘基本
  • 工作的20条吐血经验

    万次阅读 热门讨论 2009-10-30 09:01:00
    今天刚刚看到,觉得原作者写的比较用心,挺贴切的,转过来和大家share一下。...我工作快两年了,回顾一下自己的经历,写了点东西。大家也一起来说说经验吧,共同交流一下,互相促进,增长一点本领。 经验一:
  • 轻量级工作流引擎的设计与实现

    千次阅读 热门讨论 2004-08-15 19:28:00
    在设计工作流引擎主要考虑对其数据模型的定义和解释、活动之间的协调以及任务的分配和控制等功能提供支持,而不支持诸如提供内建(built-in)的应用开发工具、对应用资料的定义和完整性维护、完善的异常处理以及...
  • 最高境界的工作

    万次阅读 2009-06-02 08:37:00
    这意味着当企业处于下滑边缘要坚持,在销量下降或关于人的问题增多要坚持。吉姆的箴言是: “ 为了解决问题,我将尝试 10 种、 15 种、 20 种、 30 种不同的方法。 ”  这些事迹肯定了当今经营管理活动中一...
  • 售前工作职责和流程

    万次阅读 2012-06-04 16:12:17
    也就是说,在售前工程师工作时,有个前提假设,即用户需求的是已经得到确定或者是已经成型。而售前咨询师的工作大部分是通过对用户当前业务或者管理状况的分析,提出用户信息化的架构和策略,并且,根据此架构和策略...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 80,039
精华内容 32,015
关键字:

工作时长怎么填写