前端部署多node服务

文档目的:

实现可以在多台服务器部署前端代码;

系统设计

前端<------>node<------>后端(java)  
node层完成两个功能:(1)服务端渲染;(2)restApi接口处理

技术架构

react + redux + node + express

改前的校验逻辑  

  1.  采用token校验 
  2. 后端(java端) 把token和用户信息 以data形式发给前端(node层),前端(node层)保存到了session中,并在restApi接口中,从session获取这个token保存到req.header中,从session中读取用户信息,如用户名、是否为企业等字段放到某些需要的接口中
  3. 用户信息保存在session中。session为express-session,也就是保存在了node服务器上

解决方案(被pass掉了)

  1. 如果仍然使用session方式。解决方案:ip_hash方法解决。
  2. 这种方案呢,放弃了一些点背的用户。意思是:当用户a登录的时候,用户信息保存到了服务器1上面;这时停掉了服务器1;用户a发送请求,获取不到token,可以认为发送过去的是空或者undefined;于是后端返回403,过期或超时等,跳出登录。
  3. 如果这个场景可以接受的话,原则上是可行的。测试环境也没有发现异常,但是线上有一个用户反馈串号了,当时觉着影响太大,立刻回滚了,没有复现问题
  4. 然后现在的问题是,查看了数据库操作记录,n(至少>10)多个用户操作都没有问题,有一个用户反馈他那串号了,但是呢,我们的开发和测试都无法复现问题, 然后以回滚告终。
  5. 到底为啥串号了,无疾而终……  
猜想:如果串号问题存在,很有可能是由于session在node层的缓存导致的

现在要二次做该需求,是重新上线上次的版本,还是换一个思路做呢?忐忑……
(PS:最吓人的不是需求太难,开发不了,而是根本复现不了问题,无从下手呀呀呀呀呀呀)

提出问题

  • 既然重新做,就要雨露均沾,照顾所有用户。该怎么做呢?
  • 部署多台服务器,用户信息不能保存到node服务器上;
原因很简单,当一台服务器停掉和上线,如果用户a登录信息保存到了对应被停掉的服务器上,那么该用户登录会发生异常,比如退出登录等
  • 那么该保存到哪里呢?
方案有两个:数据库(如Redis)和前端


Redis被弃

  1. Redis 是一个高性能的key-value数据库
  2. 它的优势之一在于能够供多进程共享,有完善的备份和恢复机制。 这样听来好像很适合
  3. 但是只是在处理用户信息时使用Redis会显的太“笨重”了
  4. 而且token的校验是在后端(java端)维护的,前端只是在发送请求时,带着用户信息(包括token),没必要增加一个数据库依赖
  5. 综合3和4两点,所以该方案被放弃了。

保存到前端的方案

  1. 保存到store(实际是redux-store)中
  2. 保存到cookie中.  
下面针对这两个方案,详细介绍下解决该问题的思路。

方案讨论


  方案一:store(被弃)
  1. 如果在登录成功后,用户信息就保存到store中。每个restApi接口,用户信息从store中获取
  2. 一台服务器时运行正常
  3. 多台服务器时,每个api接口,发现在node层无法从store中获取到用户信息,打印为undefined
  4. 这个也可以理解。毕竟store是前端的,node层属于服务器端,单台服务器可以,猜想是缓存导致的(未验证,仅是猜想)
 方案二:继续store存储方式(被弃)
  1. 然后考虑在前端的store中获取用户信息,并添加到接口中
  2. 一个原因:涉及到用username,email、iscompany等字段的restApi接口要单独去改;
  3. 二个原因:token的生成规则,会用到ip。也就是说ip是必须发的,那么每个接口都要发送ip这个参数,不符合用户习惯。即使功能可以实现。
  4. 尤其第二个原因,所以排除了
 方案三:cookie(被弃)
  1. 考虑用cookie保存用户信息和token. 
  2. 用户登录成功后,前端(node)收到后端返回的用户信息(包括token),保存到cookie中
    const cookieAge = 1 * 60 * 60 * 1000;
        res.setHeader('Set-Cookie',
          [
            `TOKEN=${userInfo.token}`,
            `NAME=${Number(username)}`,
            `EMAIL=${userInfo.email}`,
            `ISCOMPANY=${userInfo.company}`,
          ],
          'Secure',
          `Max-Age=${cookieAge}`
        );
  3. 新的问题又来了,由于同一浏览器是共用cookie的。
  4. 也就是说,登录两个账号a和b,cookie会都变成b的,所以a账户,必须更新成b,否则容易账户之间数据混乱
  5. 解决方案是:在用户行为action和代码执行reducer之间,加了一个中间件,捕获当前cookie和当前页面store中的token是否一致,不一致,则更新用户信息
    // 添加中间件api
    export default function configureStore(preloadedState) {
      return createStore(
        rootReducer,
        preloadedState,
        applyMiddleware(thunk, api(httpClient), auth)
      )
    }
    
    // 中间件api.js。在代码执行开始,添加如下代码片段
    if (Cookies.get('TOKEN')) {
          next(UpdateUserInfo({
            token: Cookies.get('TOKEN'),
            name: Cookies.get('USERNAME'),
            email: Cookies.get('EMAIL'),
            iscompany: Cookies.get('ISCOMPANY'),
          }))
       }
       其中 UpdateUserInfo是一个action,用来更新store中用户信息
  6. 但是问题又来了。。。该项目是单页面应用,所有功能都是异步请求的。意思是,点击某个按钮,只更新该按钮涉及到的内容,其他内容不更新
  7. 这样容易造成混乱。比如a账号下,显示的是b账号的信息,会误导用户
  8. 于是又要想解决方案了,除非操作a页面时,发现cookie和store中token不一致,就更新所有的接口信息(不现实,而且不符合代码习惯).而且如果全部都更新了,和刷新页面无异
  9. 于是,强制刷新页面。那涉及到操作a页面时强制刷新
  10. 当时觉着操作a页面强制刷新,用户体验不太好,是否可以考虑跳转到未登陆页
 方案四:继续cookie
  1. 跳转到未登陆页的话,解决方案是:加一个中间件,任何用户行为,都会先经过中间件,判断cookie中和store的token是否一致,不一致就跳转到未登陆页
  2. 针对刚才提到的,同一个浏览器登录两个账号问题:我理解的同一浏览器、token不同,跳出是正常的。
  3. 但是,同一浏览器登录同一账号多次,用户名相同,token不同。也跳出登录。当然这个地方也可以改成判断用户名一致,token不一致,退出登录。这里我用的前者。
    // 添加中间件updateUser
    export default function configureStore(preloadedState) {
      return createStore(
        rootReducer,
        preloadedState,
        applyMiddleware(thunk, updateUser(preloadedState), api(httpClient),  auth)
      )
    }
    // updateUser.js
    import Cookies from 'js-cookie';
    import { loginPage } from '../../middlewares/links';
    export default function updateUser(preloadedState) {
      return () => next => (action) => {
        if (preloadedState &&
          preloadedState.auth &&
          Cookies.get('TOKEN') !== preloadedState.auth.user.token) {
          window.location.href = loginPage;
        } else {
          return next(action);
        }
      };
    }
    以上内容,均为个人经验。如果不当或者更好的建议,欢迎评论

你可能感兴趣的:(web开发)