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基本微服务构建(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干货:SpringCloud简介
微服务之SpringCloud实战:SpringCloud Eureka服务治理