SpringCloud微服务拆分实战之大二 (下)Java期末大作孽

Posted Huterox

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringCloud微服务拆分实战之大二 (下)Java期末大作孽相关的知识,希望对你有一定的参考价值。

文章目录

前言

由于时间关系,这次期末作孽只能做一个非常简单的玩意,由于期末作业可以在阶段作品的基础上进行优化,于是我有个大胆的想法,为什么我不阔以直接把SpringBoot 构建的单体项目拆分为微服务架构,这个也是一个升级嘛,顺便复习一下微服务。

本次微服务拆分的案例是:嘿从零开始基于SpringBoot 打造在线聊天室(4.4W字最长博文)

(PS:主要是为了对付期末作业,如果没事的话,别这样乱拆)

这个是基于vue + springBoot 整合mybatis websocket 做的在线聊天室(这里直接使用Map来代替redis,所以直需要mysql做存储)

由于我们只是对后端进行技术升级,所以,我们前端基本上不用动,甚至连接口都不用换。这充分体现了前后端分离的第一个好处呀。那第二个好处咧?当然是基于vue更加方便去嫖组件呀!

所以页面还是这个样子:

这个样子

and this:

项目创建

废话不多说,我们重新创建我们的项目。这部分内容可以参考这两篇博客,我这里就不复述了。

SpringCloud微服务项目搭建流程

SpringCloud基本微服务构建(Eureka+GateWay)

我们这里先创建出四个module, 分别是登录,注册,Eureka,GateWay。

这里呢,我把登录和注册拆分为两个模块了,也就是这样。

之所以这样拆呢其实也很简单,如果后面有微信登录,注册,qq邮箱等等第三方验证登录的时候,业务肯定很复杂,所以这两个模块还是需要拆分的。

但是在这里的话,又有个问题,因为登录和注册在很多方面又是相互耦合的,例如User。你登录的时候需要User实体,同样注册也需要呀,如果拆分,那么可能就需要每一个模块都需要User,例如在这里:

那么这里的话如果不优化,那么就会出现代码重复。

那么如何优化呢,其实也很简单,我们单独把User服务再拆分出来,也就是这样:

不过,这里为了省事,我就不拆了,毕竟这玩意我就是闹着玩,没办法交个期末作业,而且我这样做压根就不算优化。

登录/注册拆分

这里忘了说了,我这里拆的话还是每一个服务在共用同一个数据库,懒得再拆表了。

注册模块

注册模块的话比较好拆分
因为用到的东西很少。

就是做一个简单的插入

Controller代码


@Controller
@ResponseBody
public class Register 

    @Autowired
    RegisterMessage registerMessage;
    @Autowired
    UserService userService;

    @PostMapping("/register")
    public RegisterMessage Register(@RequestBody Map<String, Object> userMap) throws Exception 
        String account = (String) userMap.get("account");
        String username = (String)userMap.get("username");
        String password = (String) userMap.get("password");
        if(account!=null && password!=null)
            userService.addUser(account,username,password);
            registerMessage.setFlag(1);
        else 
            registerMessage.setFlag(-1);
        
        return registerMessage;
    


Server代码

@Service
public class UserService 

    @Autowired
    UserMapper userMapper;
    public User selectUserById(Integer id)return userMapper.selectUserById(id);
    public User selectUserByAccount(String account)
        return userMapper.selectUserByAccount(account);
    
    public User selectUserByAccountAndPassword(String account,String password)
        return userMapper.selectUserByAccountAndPassword(account,password);
    
    @Transactional(rollbackFor = Exception.class)
    public int addUser(String account,String username,String password) throws Exception 
//        发生异常回滚
        int flag = 1;

        try 
            userMapper.AddUser(account, username, password);
        catch (Exception e)
            flag = -1;
            throw new Exception("用户添加异常");
        
        return flag;
    


就这两个核心的。

登录模块

这个登录模块就稍微复杂了一点,因为还有个token生成,并且登录后进入聊天室也是在这里做的,所以,功能复杂一点。

对比原来的项目,就少了个聊天模块

然后这里保留的话,也是保留了比较多的东西的

拦截器保留

持久层保留

这部分的话也是,其实就是保留User和Friend,看过原来的单体项目的应该知道,这个friend其实室没事用的,本来要做的,但是没去做,以后再说吧。

服务层

之后的话,服务层和注册的不太一样,不过也是原来单体架构里面的服务层一样的


@Service
public class UserService 

    @Autowired
    UserMapper userMapper;
    public User selectUserById(Integer id)return userMapper.selectUserById(id);
    public User selectUserByAccount(String account)
        return userMapper.selectUserByAccount(account);
    
    public User selectUserByAccountAndPassword(String account,String password)
        return userMapper.selectUserByAccountAndPassword(account,password);
    
    @Transactional(rollbackFor = Exception.class)
    public int addUser(String account,String username,String password) throws Exception 
//        发生异常回滚
        int flag = 1;

        try 
            userMapper.AddUser(account, username, password);
        catch (Exception e)
            flag = -1;
            throw new Exception("用户添加异常");
        
        return flag;
    


至于工具类,这个就保留了两个

网关GateWay配置

做完这些之后的话,还需要再配置一下网关,主要是为了做跨域。
两个方案。

一个是配置文件:


      globalcors: # 全局的跨域处理
        add-to-simple-url-handler-mapping: true # 解决options请求被拦截问题
        corsConfigurations:
          '[/**]':
            allowedOrigins: # 允许哪些网站的跨域请求
              - "http://localhost:8090"
            allowedMethods: # 允许的跨域ajax的请求方式
              - "GET"
              - "POST"
              - "DELETE"
              - "PUT"
              - "OPTIONS"
            allowedHeaders: "*" # 允许在请求中携带的头信息
            allowCredentials: true # 是否允许携带cookie
            maxAge: 360000 # 这次跨域检测的有效期

还有一个,自然就是配置类

@Configuration
public class CorsConfig 
    @Bean
    public CorsWebFilter corsFilter() 
        CorsConfiguration config = new CorsConfiguration();
        config.addAllowedMethod("*");//允许所有请求头
        config.addAllowedOrigin("*");//允许所有请求方法,例如get,post等
        config.addAllowedHeader("*");//允许所有的请求来源
        config.setMaxAge(360000L);
        config.setAllowCredentials(true);//允许携带cookie

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());
        source.registerCorsConfiguration("/**", config);//对所有经过网关的请求都生效

        return new CorsWebFilter(source);
    


前端设置

这里做了之后我们前端还要再设置一下,让请求走网关。

我的网关的完整配置如下:

server:
  port: 8000


spring:
  application:
    name: gateway
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true

      globalcors: # 全局的跨域处理
        add-to-simple-url-handler-mapping: true # 解决options请求被拦截问题
        corsConfigurations:
          '[/**]':
            allowedOrigins: # 允许哪些网站的跨域请求
              - "http://localhost:8090"
            allowedMethods: # 允许的跨域ajax的请求方式
              - "GET"
              - "POST"
              - "DELETE"
              - "PUT"
              - "OPTIONS"
            allowedHeaders: "*" # 允许在请求中携带的头信息
            allowCredentials: true # 是否允许携带cookie
            maxAge: 360000 # 这次跨域检测的有效期

      routes:
        - id: loginclient
          uri: lb://loginclient
          predicates:
            - Path=/loginClient/**
          filters:
            - StripPrefix=1

        - id: registerclient
          uri: lb://registerclient
          predicates:
            - Path=/registerClient/**
          filters:
            - StripPrefix=1




eureka:
  client:
    service-url:  # eureka的地址信息
      defaultZone: http://127.0.0.1:10086/eureka

然后再我的前端的话也是原来做了跨域处理的,但是GateWay的话需要重新设置一下,主要是让GateWay通过一下Axios请求

然后在我的登录,注册模块就是这样的:

到这里的话我们的登录注册功能其实就改造完成了。接下来是聊天改造。

聊天拆分

这部分也是分两个部分,一个是聊天的信息加载,还有一个是,websocket 网络聊天。

聊天拆分的话,这里我们将引入RabbitMQ,用来做聊天信息的持久化,原来是,你发了我就存,这样做可以,但是qps多了就炸了,所以引入消息队列。当然这里的话我们还是使用mysql来做存储,实际上我们应该使用mangodb的,但是作业要求是mysql,而且mysql也可以勉强完成工作,大不了SpringBoot 那里再开启缓存嘛,降低一下查询压力(只要没有发送新消息) 而且这里还能再拆分,那就是在线聊天人员统计,管理这一块,不过不能再搞了,因为容易被锤~

当然最重要的原因是:

一个SpringBoot 起来就烧掉我起码100MB 的内存。技术除了牛逼之外,我觉得省钱也是很重要的。
(这也是为什么要搞个好点的电脑搞开发,不然你连idea可能都养不起)

那么这里的话,其实还是要简单一点,当然本来我是打算这样的:

但是成萧何,败也萧何,当初SpringBoot单体项目构建的时候图方便,没怎么考虑,就是单纯用Map实现,然后用Session做服务验证。没有考虑使用redis存储,而在我们分布式微服务当中,如果我们把服务拆分了,那么对应的是不同的域名(端口) 这样一来,session无法做到跨域。如下图:

而解决方法也是redis,SpringBoot 提供了一套方案,把不同的域名的session存起来,然后设置那些域名公用一套session,主要是里面的值嘛(当然我们也可以自己实现,提取Module组件,搞一个全局Map存session)

Login服务保留聊天(跨域问题)

所以没有办法,我们任然需要Login服务保留聊天服务,这个时候也许叫Login服务已经不合适了,因为这哪是Login服务呀,但是这里我们就先姑且叫这个为Login服务吧,不改了,费劲。

完整的项目如下:

我们直接保留聊天的在LoginClient里面,于是:

那么这个时候,就不用做拆分了嘛,当然不是。

别忘了,我们存储聊天记录的时候是即使的,这样做是很容易蹦掉的,所以,这里我们引入消息队列。

引入RabbitMQ

我们专门对LoginClient服务做如下改动
首先是配置

<!--        消息转化器-->
        <dependency>
            <groupId>com.fasterxml.jackson.dataformat</groupId>
            <artifactId>jackson-dataformat-xml</artifactId>
        </dependency>





        <!-- amqp 消息队列依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>

然后是配置类,这个主要是用来做消息对象序列化转为json的,因为默认是传输Java对象的时候会变成序列化字符。这里为了节约空间和资源,转为json


@Configuration
public class JsonMQ 

    @Bean
    public MessageConverter jsonMessageConverter()

        return new Jackson2JsonMessageConverter();
    


最后是我们不在我们这里进行存储了,而是专门让一个服务存储

RabbitMQ配置

我在RabbitMQ 里面专门创建了一个用户

  rabbitmq:
    host: 127.0.0.1
    port: 5672
    virtual-host: /huterox
    username: huterox
    password: 865989840

在消息发送者,和消费者里面都这样设置就完了。

消费者存储

我们这个时候看到这个玩意

其实没啥,就是把原来消息存储的那些Server搞过来了

此外,我们还要在这里创建一个队列,当然也可以直接用注解,只是我这里习惯配置类,

总结

到此,一个很简单的微服务拆分案例就无了,不过现在这个拆分方法并不好,最理想的方案是拆分,5个微服务,登录,注册,用户管理,聊天,聊天存储。然后用户管理服务既作为一个服务,也作为一个组件Moudle,当然这里也能单独把用户相关的pojo,之类的单独作为一个组件,然后登录,注册,用户管理(CURD)来使用它,然后三者用feign通信。 然后就是聊天和存储,这个就是消息队列干的事情,不过他们之间还需要一个聊天消息的服务可以拆出来,或者拆出来一个组件,至于跨域就在用redis。

但是我这里不这样干,原因很简单,本来没啥好拆的,我就是单纯为了期末作业,并且期末作业要演示,用的东西多了我本地不好演示,我的内网服务器没法在教室访问,因为网段不一样,网络服务器,没有小钱钱了啦丫!

以上是关于SpringCloud微服务拆分实战之大二 (下)Java期末大作孽的主要内容,如果未能解决你的问题,请参考以下文章

SpringCloud微服务之初识微服务01

SpringCloud微服务之初识微服务01

微服务之SpringCloud干货:SpringCloud简介

微服务之SpringCloud实战:SpringCloud Eureka服务治理

微服务架构整理-(七SpringCloud实战之RestTemplate)

微服务架构整理-(七SpringCloud实战之RestTemplate)