热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

微服务拆分之无锁编程

微服,务,拆

介绍
如果你受够了微服务系统中无休无止的痛苦,哪些数据库事务,分布式锁,永无止境的系统优化,莫名其妙的卡死,诡异的性能波动。来尝试一下最新的无锁编程技术吧。这个技术最酷的地方就是不需要数据库事务和分布式锁就能实现分布式系统的开发。众所周知分布式锁和数据库事务的滥用导致了分布式系统耦合的问题。
我在这个系列的第二篇文章中曾经对一个开源的电商软件进行了分布式的系统分析。您可以点击下面链接找到这篇文章。
ITDSD - 1. Splitting in Microservice Architecture
在这里我已经使用AP&RP理论将这个工程改造为分布式系统。在服务端软件开发的过程中。随着用户数量的增加我们都会遇到服务端性能的瓶颈。为了解决服务端性能的瓶颈需要拆分服务端到不同的硬件以提高集群整体的承载能力。没有AP&RP理论之前这种服务端的拆分非常低效。通常人们引入大量的数据库事务和分布式锁,这些数据库事务错综复杂,并最终使人们迷失在系统耦合中。通过学习AP&RP理论可以让你具备编写无锁分布式系统的能力。本文所使用的实例就是第一个使用AP&RP理论开发的无锁分布式系统。为了说明AP&RP理论的通用性,我选择了最常见的网上商场系统作为实例。因为它的复杂度适中,适合初学者学习。并公开其源代码于github.com上。链接为gantleman/shopd。或者您可以通过下载本文的附件得到它。
性能的提升
评价一个系统重构是否真的有效,通过性能对比就可以得到结论。本文是通过对Manphil/shop工程进行改造得到的。原工程是一个非常简单而明确的网上商场系统。是由有一个服务端和一个数据库共同组成。服务端又由62个任务组成。
假设其服务端和数据库分别运行在两个服务器容器内。改造以前服务端的62个任务共享一个服务器容器。我们将其改造为分布式系统后。根据AP&RP理论可以将62个任务分为3个类型。第一种类型是多个任务必须放在一个服务器容器内。第二种类型是1个任务可以放在一个服务器容器内。第三种类型是1个任务可以复制多份放入任意数量的服务器容器内。
第一个类型的任务一共有30个分为8组,这8组任务只能分别放入8个服务器容器。这8组任务中最多的有7个任务,最少的有2个任务。在分布式系统改造前每个任务能分配的资源为一个服务器器容器的1/62。在系统改造后运行任务最多的服务器容器内每个任务所分配的资源为1/7。可知在进行分布式系统改造后单任务获得的服务器资源最少提升了8.8倍。同理可知第二种类型的任务可以获得的资源提升了62倍。而对第三种类型的任务因为可以复制到任意数量的服务器容器中,所以能够获得的性能提升没有限制,随着硬件的增加而增加。因为第一种类型的任务占任务总数的48%。所以对于48%的系统性能提升了8.8到62倍。而对于剩下的62%的任务可以获得无限的性能提升。
单机Berkeley DB写入极限为10万每秒。7个任务可以每个任务可以分配到1万左右。因为这个7个任务包含了订单完成的任务。所以这个网站的订单完成功能的理论承载极限为每秒1万。全球最大的电商网站的促销活动中订单数量的峰值为每秒8万。所以本次分布式改造理论上可以使软件性能达到世界顶级水平。当然这种大型分布式系统也需要大量的硬件作为支持。不能单独依靠软件系统性能的提升。
安装说明
开发环境:Ubuntu 16.04.4, vscode 1.25.1, mysql Ver 14.14 Distrib 5.7.26, redis 3.0.6, maven 3.6.0, java 1.8.0_201, git 2.7.4
软件框架:SpringBoot, JE.
安装好上述环境之后在根目录执行
>mvn install
然后将shop.sql导入到mysql数据库
>mysql -h localhost -u root -p test

如果使用VSCode编辑器需要添加launch.json文件

{ // Use IntelliSense to learn about possible attributes. // Hover to view descriptions of existing attributes. // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ { "type": "java", "name": "Debug (Launch) - Current File", "request": "launch", "mainClass": "${file}" }, { "type": "java", "name": "Debug (Launch)-DemoApplication", "request": "launch", "mainClass": "com.github.gantleman.shopd.DemoApplication", "projectName": "shopd" } ] } 

编辑application.properties文件配置数据库地址和帐户密码,以及服务器端口号。

spring.datasource.url=jdbc:mysql://localhost/shop?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true spring.datasource.username=root spring.datasource.password=123456 spring.datasource.driverClassName=com.mysql.jdbc.Driver server.port=8081 

点击F5启动调试模式。
打开浏览输入http://localhost:8081/main即可打开主页。
分布式系统的说明
这个分布式系统是由5个部分组成,分别是nginx反向代理,springboot服务器,reids内存数据库,mysql数据库,以及还没有开发完成的分布式管理器。还有一个非独立运行的JE数据库。分布式管理器在当前版本并没有一个独立的实体。其负责管理redis数据库中的routeconfig字段的数据发布。所以我们需要通过运行测试例程t7()函数,来实现routeconfig字段的数据发布。
如果按纯粹的AP&RP理论进行微服务化的拆分,那么是不需要mysql服务器的。在第一个版本实现中是遵循原AP&RP理论。首先需要将数据按读写功能进行分析。这部分工作在ITDSD2中以及完成并存储在了shop.xlsx文档,你在下载区可以找到它。被分析过的数据按读写功能分别提前存储到每个JE数据库内。用户通过nginx调用对应功能的springboot服务器。再由springboot服务将数据发布到redis内存数据库上。
在最新的版本里,为了能够有效地管理分布式系统引入了缓存机制。缓存机制可以将Mysql数据库一部分的数据读取到JE数据库。在不使用这些缓存数据后可以将数据再写回Mysql数据库。与其他分布式系统中的缓存机制不同的是。这套缓存机制使用页缓存架构。避免了使用不存在数据导致反复触发读取缓存。也使得缓存随机命中率得到了提高。其效果要好于在分布式系统逐条交换数据。
到这里我们成功地创建了一个单机的调试环境。如何进行分布式系统的部署呢?只要创建一个nginx反向代理服务器。并依据shop.xlsx文件所显示的分析结果。将可以分布的任务单独创建一个springboot服务器并修改对应的routeconfig字段。假设我们把/admin/activity/show任务单独放入一个服务器。由shop.xlsx文件可以知道/admin/activity/show任务属于可以任意复制多个类型。那么只要单独创建一个springboot服务器,并设置端口为8082并修改redis中的routeconfig字段。以及修改反向代理的nginx,使得/admin/activity/show的调用指向8082。/admin/activity/show任务就被单独的拆分出去了。
fig1

在集群状态下服务器请求由nginx反向代理服务器到springboot服务器。Springboot服务器检查是否命中页缓存。如果没有命中就从mysql服务器调入数据。当前Springboot服务器调入数据后将数据发布到redis服务器中供其它Springboot服务器读取。其它Springboot服务器读取数据redis服务器中的数据如果不存在。就触发负责管理指定数据的Springboot服务器更新缓存。注意这里如果不是负责管理指定数据的Springboot服务器是无权直接读取mysql数据库的指定数据。只能通知负责管理指定数据的Springboot服务器进行操作。
索引和缓存的代码分析
使用AP&RP理论进行微服务化的拆分本身非常简单。对于面向用户的产品逻辑更改也非常的少。在重构过程中最大的问题是构建索引和缓存页调度。Redis不支持构建索引,需要使用map和set结构自行构建。JE数据库源于Berkeley DB,其支持索引但索引结构和Myslq完全不同。把数据从Mysql数据库读入JE数据库时需要进行重新创建索引。 Mysql数据库并没有将索引数据作为单独的数据提供给使用者。只能间接的通过指令使用索引数据。这与索引数据严重依赖数据结构有关。索引数据会因为数据集合的改变而改变。所以在工程中可以看到大量构建索引和载入缓存的代码。
以getAllAddressByUser函数为例。请看代码注释

@Override public List
getAllAddressByUser(Integer userID, String url) { List
re = new ArrayList
(); ///这里查询指定userID的数据是否被载入到redis if(redisu.hHasKey(classname_extra+"pageid", cacheService.PageID(userID).toString())) { //读取redis的索引数据,这个索引内包含有userID的所有addressID Set ro = redisu.sGet("address_u"+userID.toString()); if(ro != null){ for (Object id : ro) { Address r = getAddressByKey((Integer)id, url); if (r != null) re.add(r); } ///增加索引页的引用次数,为了索引页的调度 redisu.hincr(classname_extra+"pageid", cacheService.PageID(userID).toString(), 1); } }else { ///如果指定页没有在缓存内就触发载入页面。 if(!cacheService.IsLocal(url)){ cacheService.RemoteRefresh("/addressuserpage", userID); }else{ RefreshUserDBD(userID, true, true); } ///如果载入成功就再次读取数据。 if(redisu.hHasKey(classname_extra+"pageid", cacheService.PageID(userID).toString())) { //read redis Set ro = redisu.sGet("address_u"+userID.toString()); if(ro != null){ for (Object id : ro) { Address r = getAddressByKey((Integer)id, url); if (r != null) re.add(r); } redisu.hincr(classname_extra+"pageid", cacheService.PageID(userID).toString(), 1); } } } return re; }

缓存的载入和索引的创建通常的混合在一起。如果索引没有命中需要载入相关的索引数据的缓存。因为存在分布式架构所以每个服务器只存储了部分数据。所以要严格禁止对数据进行全局检索。例如动态检查用户名下全部地址。所以我在数据库内创建address_user表用于储存用户名下所有地址的id。如果没有address_user就需要每次查询用户地址的时候进行一次数据库address表的全局查询。因为address_user的索引数据存在。我只要获得对应用户的索引数据就知道他的全部地址id了。
缓存的管理是通过cacheService实现的。我使用了表的ID作为缓存的分割标致。这样可以通过ID方便的计算出当前所在页面。当然这样的简化的写法会导致名字,类别,商品搜索等纯文字类的检索功能无法实现页缓存。商品搜索以后会通过大数据的方式实现。使用名字的数据可以通过bit矩阵的方式。这些可能会在后续的改进中实现。
结论
这是首个在分布式中实现无锁编程的示例,让我遗憾的是他还不是一个框架。对于希望在分布式工程中实现无锁编程的程序员。这个示例可以作为一个很好的开始。使用类似设计的分布式系统将不会再受到分布式锁与数据库事物带来的耦合影响。因为遵循AP&RP理论所设计的系统不存在分布式锁和数据库事物。超大型的多人互动系统的软件开发将不在高不可及。普通的程序员也可以轻易的上手。我想这将会很好地推动服务器端软件的普及工作。


推荐阅读
  • Oracle优化新常态的五大禁止及其性能隐患
    本文介绍了Oracle优化新常态中的五大禁止措施,包括禁止外键、禁止视图、禁止触发器、禁止存储过程和禁止JOB,并分析了这些禁止措施可能带来的性能隐患。文章还讨论了这些禁止措施在C/S架构和B/S架构中的不同应用情况,并提出了解决方案。 ... [详细]
  • 篇首语:本文由编程笔记#小编为大家整理,主要介绍了软件测试知识点之数据库压力测试方法小结相关的知识,希望对你有一定的参考价值。 ... [详细]
  • Centos下安装memcached+memcached教程
    本文介绍了在Centos下安装memcached和使用memcached的教程,详细解释了memcached的工作原理,包括缓存数据和对象、减少数据库读取次数、提高网站速度等。同时,还对memcached的快速和高效率进行了解释,与传统的文件型数据库相比,memcached作为一个内存型数据库,具有更高的读取速度。 ... [详细]
  • 2021最新总结网易/腾讯/CVTE/字节面经分享(附答案解析)
    本文分享作者在2021年面试网易、腾讯、CVTE和字节等大型互联网企业的经历和问题,包括稳定性设计、数据库优化、分布式锁的设计等内容。同时提供了大厂最新面试真题笔记,并附带答案解析。 ... [详细]
  • 如何用UE4制作2D游戏文档——计算篇
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了如何用UE4制作2D游戏文档——计算篇相关的知识,希望对你有一定的参考价值。 ... [详细]
  • 图解redis的持久化存储机制RDB和AOF的原理和优缺点
    本文通过图解的方式介绍了redis的持久化存储机制RDB和AOF的原理和优缺点。RDB是将redis内存中的数据保存为快照文件,恢复速度较快但不支持拉链式快照。AOF是将操作日志保存到磁盘,实时存储数据但恢复速度较慢。文章详细分析了两种机制的优缺点,帮助读者更好地理解redis的持久化存储策略。 ... [详细]
  • 本文介绍了高校天文共享平台的开发过程中的思考和规划。该平台旨在为高校学生提供天象预报、科普知识、观测活动、图片分享等功能。文章分析了项目的技术栈选择、网站前端布局、业务流程、数据库结构等方面,并总结了项目存在的问题,如前后端未分离、代码混乱等。作者表示希望通过记录和规划,能够理清思路,进一步完善该平台。 ... [详细]
  • 一句话解决高并发的核心原则
    本文介绍了解决高并发的核心原则,即将用户访问请求尽量往前推,避免访问CDN、静态服务器、动态服务器、数据库和存储,从而实现高性能、高并发、高可扩展的网站架构。同时提到了Google的成功案例,以及适用于千万级别PV站和亿级PV网站的架构层次。 ... [详细]
  • 本文介绍了Redis中RDB文件和AOF文件的保存和还原机制。RDB文件用于保存和还原Redis服务器所有数据库中的键值对数据,SAVE命令和BGSAVE命令分别用于阻塞服务器和由子进程执行保存操作。同时执行SAVE命令和BGSAVE命令,以及同时执行两个BGSAVE命令都会产生竞争条件。服务器会保存所有用save选项设置的保存条件,当满足任意一个保存条件时,服务器会自动执行BGSAVE命令。此外,还介绍了RDB文件和AOF文件在操作方面的冲突以及同时执行大量磁盘写入操作的不良影响。 ... [详细]
  • 本文介绍了OpenStack的逻辑概念以及其构成简介,包括了软件开源项目、基础设施资源管理平台、三大核心组件等内容。同时还介绍了Horizon(UI模块)等相关信息。 ... [详细]
  • 一次上线事故,30岁+的程序员踩坑经验之谈
    本文主要介绍了一位30岁+的程序员在一次上线事故中踩坑的经验之谈。文章提到了在双十一活动期间,作为一个在线医疗项目,他们进行了优惠折扣活动的升级改造。然而,在上线前的最后一天,由于大量数据请求,导致部分接口出现问题。作者通过部署两台opentsdb来解决问题,但读数据的opentsdb仍然经常假死。作者只能查询最近24小时的数据。这次事故给他带来了很多教训和经验。 ... [详细]
  • 本文介绍了关系型数据库和NoSQL数据库的概念和特点,列举了主流的关系型数据库和NoSQL数据库,同时描述了它们在新闻、电商抢购信息和微博热点信息等场景中的应用。此外,还提供了MySQL配置文件的相关内容。 ... [详细]
  • ElasticSerach初探第一篇认识ES+环境搭建+简单MySQL数据同步+SpringBoot整合ES
    一、认识ElasticSearch是一个基于Lucene的开源搜索引擎,通过简单的RESTfulAPI来隐藏Lucene的复杂性。全文搜索,分析系统&# ... [详细]
  • 面试经验分享:华为面试四轮电话面试、一轮笔试、一轮主管视频面试、一轮hr视频面试
    最近有朋友去华为面试,面试经历包括四轮电话面试、一轮笔试、一轮主管视频面试、一轮hr视频面试。80%的人都在第一轮电话面试中失败,因为缺乏基础知识。面试问题涉及 ... [详细]
  • 云原生应用最佳开发实践之十二原则(12factor)
    目录简介一、基准代码二、依赖三、配置四、后端配置五、构建、发布、运行六、进程七、端口绑定八、并发九、易处理十、开发与线上环境等价十一、日志十二、进程管理当 ... [详细]
author-avatar
书友41494390_263
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有