微服务
之前在写SpringBoot的笔记时,就有提及到SpringCloud。SpringCloud提供了微服务的开箱即用。微服务近年来非常火,到处都在说微服务。笔者也对微服务相当感兴趣,因为笔者在校期间(N年前)曾经和很多同学聊过,如果所有的应用并不是单体的,而是通过很多系统提供API这会变成怎么样,当时我就觉得这样能够做到分布式服务。因为服务是分离的,我们可以针对每一个不同的服务,调整负载均衡的设备数量,提供更多的资源给到负载较重的服务。但是在考虑的时候也发现了很多技术的问题,例如如何在多个服务器提供强一致性的事务管理,服务服务之前应该通过什么方式去调用(HTTP?NIO?AMQP?),太多太多的问题导致我认为当初这个想法是不切实际的,可能是以前技术水平并不高的原因吧··· 虽然现在也不高。庆幸的是,N年之后的今天高流量负载的互联网推动了微服务。当年我想的问题,至今已经有了非常多相应的解决方案,让我对微服务充满了未知的兴趣。
为什么我会选择写SpringCloud的笔记?目前有很多很优秀的微服务框架,例如阿里的dubbo。很简单因为dubbo有很详细的中文文档,想学或者忘记了可以查dubbo的文档,但是对比与SpringCloud,笔者认为dubbo性能会更佳,因为SpringCloud 主要的通信协议使用HTTP(REST),AMQP。但是笔者因为HTTP的短板是每次通信都是需要建立连接效率上还是有所欠缺,而dubbo是提倡使用基于NIO的通信,对于大吞吐量有非常大的优势。但是不管怎么说,SpringCloud也是相当优秀的框架,在近些年来SpringCloud的版本发布速度也是快得离谱,详细SpringCloud会越来越完善。而且SpringCloud基于SpringBoot提供给我们非常简单的配置和操作,学习一门分布式框架有助于你理解分布式的其他架构理论,毕竟打过仗再去看兵法,比纸上谈兵效果很好嘛····
其实在分布式最为困难的是,如何去换分服务。这是非常困难的,往往需要非常多经验的沉淀。笔者不才~ 但是个人觉得,按照服务器是否存在强一致性去划分比较好。因为网络是不稳定的、容易出错的,所以对于一些强一致性的业务最好不要分。不过,如果出现边界模糊、服务过于复杂你也得分,但是这样你就要在容错技术上做好充分的准备,如何做事务补偿机制、如何保证一致性都是等着你的问题。但是笔者对这部分还是有相当多的空白,所以笔者在外来一段时间会看一些关于领域驱动设计的书籍,如果觉得不错,也会CSDN写相关的博客,也会推荐相关的书籍。
写了那么多,第一次写这么多理论的东西。不管怎么样,我会在我的笔记尝试解答着一切,以后无论是其他开发者看到我的笔记也好,或者是我自己工作遇到这些技术需求也好,希望这些笔记可以帮助到我或者你们。
我们开始之前如果你对SpringBoot不熟悉,可以移步先看看SpringBoot【SpringBoot系列(1)---无配置文件配置基础1】,如果你连Spring都不熟悉,也可以先看看Spring。
一、初试微服务
先说说业务,目前笔者所在的团队主要是开发电商系统的,所以我将会以电商的业务作为业务模型,相信都网购过也不用多说业务上的东西了。
我们会创建三套服务,商品服务、用户服务、订单服务 其他什么仓储物流、促销系统、评价服务、团购等等就不搞了。简单明了去认识服务。
简单点,商品服务提供商品查询,有两个参数用户ID和商品ID,根据用户ID从用户服务中获得用户信息(通常都会添加商品访问日志用于数据分析、通过促销系统、验证优惠信息操作都需要用到当前登录的用户信息)由于方便,我们就不用Redis做用户登录的token缓存了,实际环境自己发挥·····
先创建两个项目,一个是商品服务系统、第二个是用户服务系统。
在用户服务系统我定义了两个接口,以下是接口的Controller:
@RestController public class UserController { @Autowired private UserRepository userRepository; @GetMapping("/getUserById/{id}") public User getUserById(@PathVariable int id){ return this.userRepository.getByUserId(id); } @PutMapping("/save") public User addUser(String username,String password,long balance){ User user = new User(username,password,balance); this.userRepository.save(user); return user; } }
在商品服务系统,我定义也定义了两个接口,以下是商品服务系统的Controller:
@RestController public class ProductController { @Autowired private ProductRepository productRepository; @Autowired private RestTemplate restTemplate; @PutMapping("/add") public Product addProduct(String productName, String productDesc, long productPrice) { Product product = new Product(productName, productDesc, productPrice); return this.productRepository.save(product); } @GetMapping("/getProduct/{productId}/userId/{userId}") public Map<String, Object> getProduct(@PathVariable int productId, @PathVariable int userId) { Map<String, Object> map = new HashMap<>(); String url = "http://localhost:8801/user/getUserById/" + userId; User user = this.restTemplate.getForEntity(url, User.class).getBody(); Product product = this.productRepository.getByProductId(productId); map.put("user",user); map.put("product",product); return map; } }
使用RestTemplate之前,必须定义相关的bean,以下是商品服务系统的配置类:
@SpringBootApplication public class ProductsystemApplication { public static void main(String[] args) { SpringApplication.run(ProductsystemApplication.class, args); } @Bean public RestTemplate restTemplate(){ return new RestTemplate(); } }
虽然我们上面的getProduct接口没有什么实际意义,但是已经体现了微服务的技术点。由于笔者没有分开两台计算机去开发两个项目,而是在同一台计算机使用不同的端口号运行两套系统,以下笔者当前两个项目的application.properties配置文件:
用户服务系统配置文件如下:
server.port=8801 server.context-path=/user spring.datasource.url=jdbc:mysql://localhost:3306/shop_mall?charset=utf8mb4 spring.datasource.driver-class-name=com.mysql.jdbc.Driver spring.datasource.username=root spring.datasource.password=tonyyan
商品服务系统配置文件如下:
server.port=8802 server.context-path=/product spring.datasource.url=jdbc:mysql://localhost:3306/shop_mall?charset=utf8mb4 spring.datasource.driver-class-name=com.mysql.jdbc.Driver spring.datasource.username=root spring.datasource.password=tonyyan
可以看到笔者两个项目都是连接相同的数据库,但是在实际的微服务上大可不必。由于数据库的连接池非常容易在极端场景打满,所以不同的服务可以使用不同的数据库作为持久化层,而且可以针对每个服务的特性去选择数据库也是非常常见的做法,如果数据量大,查询速度上有要求,也可以在这些服务系统上使用mongodb或者hbase等NOSQL数据作为持久化层。
测试接口:http://localhost:8802/product/getProduct/1/userId/1
JSON返回如下:
{
"product": {
"productId": 1,
"productName": "Iphone",
"productDesc": "new Iphone",
"productPrice": 700000
},
"user": {
"userId": 1,
"username": "TONY",
"userpwd": "TONYPWD",
"balance": 1000000
}
}
简单的我们已经可以发现几项问题:
1、我们的访问URL是写死的,如果目标服务有变,访问接口也需要变。
2、访问的服务器不是高可用的,更加不用说负载均衡了。
下一遍笔记将会解决上述两大问题。