赞
踩
1-1 微服务
1-2 软件架构的进化
什么是软件架构?软件架构是在软件的内部,经过综合各种因素的考量、权衡,选择特定的技术,将系统划分成不同的部分并使这些部分相互分工,彼此协作,为用户提供需要的价值。
什么是单体架构,定义:功能、业务集中在一个发布包里,部署运行在同一进程中。
单体架构的优势:
易于开发、易于部署、易于测试、易于水平伸缩
单体架构面临的挑战:
代码膨胀,难以维护;构建、部署成本大;新人上手困难;创新困难;可扩展性差。
1-3 什么是微服务?
使用一套小服务来开发单个应用的方式,每个服务运行在独立的进程里,一般采用轻量级的通讯机制互联,并且它们可以通过自动化的方式部署。
微服务的特征:单一职责(登录注册);轻量级通信(平台/语言无关);隔离性;有自己的数据;技术多样性。
1-4 微服务架构图
假定业务场景:一个在线教育的网站的部分功能;用户可以登录注册,获取用户信息;有发送邮件或短信的功能;可以查看课程列表和对课程的基本CRUD。
1-5 微服务架构的优势和不足
优势:独立性、敏捷性、技术栈灵活、高效团队。
不足:额外的工作(服务的拆分)、数据的一致性、沟通成本。
跟我学过初级和中级的小伙伴,感谢有你们的支持哈哈,我开始高级篇了。这次设计到springboot 和spring cloud,重点是落地,之前的CICD涉及的面有老铁说最后镜像部署没说,我说高级一定补上的!
微服务最近几年比较火,不了解也没关系,可以理解成一个项目的模块吧,微服务运行在docker容器里面,如果管理docker容器用服务编排框架k8s。
镜像生产环境的自动化部署
纵向的是系统的更新频率,黄色的是半年以上更新一次,绿色的部分是3-6个月,蓝色的部分是每个月都要更新。横向的是行业的细分:制造业,金融行业,互联网行业,交通物流行业,零售业。其中互联网的更新频次最高92%的服务每个月都要更新。其中应该有很多服务一个月要更新多次。我相信如果一个行业如果要有竞争的优秀,更新频率一定是在不断的提高的。这就会倒逼越来越多的企业加入转型,docker微服务就是方向。
看2个比重最大的问题,系统复杂性越来越高,IT运维管理复杂,构建一个全功能团队困难。应用频繁的升级开发团队会非常的痛苦:企业业务系统经过多年的发展,系统往往非常庞大,复杂度非常的高,要改动其中任何一个小功能都需要部署整个应用,敏捷开发和快速的服务根本无从谈起,传统行业在传统的IT建设过程中往往会使用不同的技术,这就存在了技术之前的诧异很大,管理和运维就比较复杂,随着这些问题的凸显,企业向微服务进行转型需求越来越强烈。
6%的企业应用了spring cloud开发框架,9%采用了dobbo和其他的微服务框架,51%考虑云原生的架构方向转型(公有云,私有云),因此可以看出来绝对部分企业有转型的需求的。
docker从2017年的7% 升级了4个百分点达到11%。考虑使用docker的应用的越来越多,特别是100台 服务器 以上的。
企业的关注度才不断的升高。docker的使用在不断的普及,容器的成熟,对微服务的落地提供了很多的基础,轻量化的容器是微服务的最佳环境。微服务在容器的环境下,在加上服务编排框架持续集成变成可能。
腾讯,阿里,京东,包括新浪 都在使用docker。通过图片的数据和案例说明:docker,服务编排,微服务值得我们去学习。可能在不久的将来将会是每一位开发和运维的老铁不得不了解的技术。
从实战的角度出发
1.传统服务和微服务对比的方式来进行学习。
通过业务场景。从0开始一行不拉的开发完整个项目,微服务的开发有一个深刻的体会,服务开发过程中我们会用到,dubbo,spring boot ,spring cloud,网关,thrift。
体会到:dubbo的远程调用,thrift跨语言的接口调用,spring boot快速开发。
jekens 和gitlab 中级欠大家jekens这次补给大家。目的是从代码提交到流程更新全部自动化。
PS:整体把握微服务,清晰理解微服务的各种概念,如果开发微服务,技术栈之间的微服务通信,怎么样把一个服务运行在docker容器里,服务之间是如何建立连接的,多种编排框架下服务的编排和服务的发现扩容。docker绝对是你以后必经只选。来我们一起努力,成为更好的自己。
也工作了10年了,对于软件的架构也是不断学习总结,怎么样的发展到微服务的架构。
在软件的内部,经过综合各种因素的考量,权衡选择特定的技术,将系统划分不同的部分并使这些相互分工,彼此写作,为用户提供需要的价值。
2007年在河南本地的一个公司实习,负责的是一个老系统,它用到了jsp和servlet,jdbc的技术,java早期的标准技术,在jsp里面看到了html,还看到了一大片一大片的 java 代码,直接写在jsp里面。在servlet里面有上千行的代码,300,500行都很平常的事情,包含了业务逻辑,返回给jsp的业务内容,业务操作,数据库操作。维护起来让你很崩溃,不过才毕业也就忍了,坚持了半年。后来要去济南。这种在极其简单的业务里面还是可行的,但是现在也看不到了。
2008年去了济南,济南毕竟要全国知名的公司就进入了。虽然是996,但是感觉还好,至少代码不那么复杂了,虽然是jsp,java代码基本没有,分了很多文件夹,层次清晰分工明确,也学到MVC的三层架构。解决了代码调用杂乱无章,让代码清晰,通过各层之间定义接口的方式,让接口和实现分离,可以将原来的实现替换成方案,让别人理解,降低了沟通成本,维护成本,分工的明确各司其职,很长时间都是软件的架构经典模式。像SSH 和SSM其实MVC的实现。
2013年换了一家公司,dubbo那时候才出来1年,公司尝试用dubbo改造一个核心系统,为什么要用dubbo,因为里面java代码加页面代码100多万行,需求每个月还不断的添加,牛逼了我的哥!3年以上的人至少2-3个月熟悉都不一定能上手,只能想办法拆分,拆分的过程也是对老代码进行梳理和重构,dubbo的出现可以让前后端物理上隔离开来,完全变成2个可以单独维护的模块,从感官上复杂度就下降了一半,这种开发历程,在河南这边可能不太明显,在北上广应该都有类似的经历。多年的开发的人员。
其实上边的说的都是单体架构,很多目前的公司也都是单体架构,虽然dubbo,分离成了前后2个个体,但他并不是微服务。
功能,业务集中在一个发布包里,部署运行在同一个进程中。
单体面临的挑战
>随着很多传统行业往互联网考虑,业务变化瞬息万变,系统的升级也越来越频繁,用户的数量快速增长,单体架构已经无法满足互联网的发展了,它有很多致命的硬伤。
PS:综上所述,单体架构已经out了,老铁,可以考虑其他了,如何考虑下回继续说。
上一节说了单体架构,单体架构也无法适应我们的服务,来说说微服务,看能否解决单体架构的问题。
最近两,三年才出现的新名词,虽然时间还不是很长,几乎每个软件从业人员对它有影响,也都通过微服务,很多人都意识到微服务对软件行业的影响。
定义
> 使用一套小服务来开发单个应用的方式,每个服务运行在独立的进程中,一般采用轻量级的通讯机制互联,并且他们可以通过自动化的方式部署。
可是我们语言不同,不同的语言写相同的业务可能代码量差距非常大。
人来判断,实习期的开发人员和有5年以上开发经验的人员写的代码量也是有差距的。
影响开发速度的因素太多太多,个人的经验,擅长开发的语言,对业务的理解。
实际是一种设计思路,设计思想,而不是固定的一个量
订单和支付,登录和注册,跟其他业务不太紧密的可以单独做成一个服务邮件,短信服务。
轻量级的通信协议,简单来说平台无关语言无关。http。
每个微服务在自己的内存中,相互之间不会干扰。
业务数据的独立性。每个都有自己的业务数据库,降低业务的复杂度
5.技术多样性
开发人员选择最适合的开发语言,提供出应有的api。
技术变化快,用户数量变化快
用最小的代价,做最快的迭代,得到最有用的反馈。频繁的修改测试上线。
容器技术没有成熟之前,微服务很难落地的,docker的出现解决了犹豫微服务数量的旁边运维的瓶颈。使微服务的落地成为可能。
PS:docker让微服务成为可能,感谢容器化技术的成熟!
2-1 微服务架构带来的问题
之前已经说了微服务的概念,相信老铁对微服务有了一个深刻的概念,从此以后咱们深入微服务,一步步来分析使用微服务会给我们带来哪些问题,或者说使用微服务需要解决哪些问题,以及微服务在业界的解决方案
可以考虑下,如果是单体架构会不会有这样的问题,在什么情况下服务和服务之间如何通迅,调用什么样的接口,依赖什么样的数据,单体架构这种情况是很少见的,一个系统在一个应用可能已经完成了相应的功能,也不排除一些系统的数据是来此其他的系统的,单体架构的常用的方式有几种,直接链接地址拿过来直接嵌入到页面里面,我们使用httpclient调用对方的接口拿到返回的数据,这是比较常见的方案,微服务要重点考虑,因为微服务他们接口比较多,他们的调用非常的频繁,所以我们必须事先设计好如何快捷高效的微服务通信。
单体架构如何发现彼此,用过dubbo的同学应该知道,dubbo其实就是发现一种服务,web端的调用者需要对dubbo的提供者进行一次发现的,发现是通过zookeeper等,类似一个中间人的身份,服务的提供者,提供者告诉中间人。消费者通过中间人拿到提供者的地址,就能够完成服务的发现了。如果是用dubbo直接确定微服务就可以了。但是我们使用的微服务可能涉及到各种语言读取方式,dubbo只限java语言的通信,所以彼此发现是我们需要提前设定和解决的问题。
还是从单体架构来想,这跟每个公司的方式不同,有的直接通过ftp工具直接把war包上传,执行命令执行重启;有的可能用到了自动部署工具直接从master节点通过jenkens生成war包在准生产服务器指定目录生成,没有问题然后通过脚本的方式,对拷到生产环境。然后重启。如果是微服务不一定少,一个完整的服务可能需要几十来配合修改,如果在一个个手动来进行部署运维人员都崩溃死了。所以微服务的部署更新成为我们要解决的问题。
2-2 微服务间如何通讯
说到通信可能会想到:socket,http,tcp/ip,zookeeper等等,这么多东西在一起可能会感觉比较乱,提供个思路来考虑微服务的问题,通信方式和通信协议来考虑。
通信方式
通信协议
很多人把rest api等同于 http的接口设计,其实他们不能直接化等号的,rest 是很早提出的一个概念,rest是表现层的状态转移,其实这个没几个人可以听的懂,其实rest是网络中客户端和服务端的一种交互形式,它本身就是一个抽象概念,主要是如何设计一个rest api,以http为例,就是用http协议来实现rest形式的api,
在 Web 应用中处理来自客户端的请求时,通常只考虑 GET 和 POST 这两种 HTTP 请求方法。实际上,HTTP 还有 HEAD、PUT、DELETE 等请求方法。而在 REST 架构中,用不同的 HTTP 请求方法来处理对资源的 CRUD(创建、读取、更新和删除)操作:
若要在服务器上创建资源,应该使用 POST 方法。
若要检索某个资源,应该使用 GET 方法。
若要更改资源状态或对其进行更新,应该使用 PUT 方法。
若要删除某个资源,应该使用 DELETE 方法。
消息队列,实际场景用的不太多,例如之前说的滴滴打车这种就是消息订阅的模式。
如何选择RPC框架
RPC是微服务方面最多的一种情况,也是选择比较多的情况,可选的RPC框架也非常的多,选择一个RPC框架是需要面临的问题。
长连接,短连接,单线程,多线程,线程调度算法的性能
可读的(XML,JSON),二进制(FASTJSON),为什么要考虑序列化呢,因为序列的效率直接影响到我们通信的效率,扩大了序列化和反序列化的时间,RPC的效率,同一个对象如果序列化小的话大大提升效率。
根据团队语言,如果是多语言就需要找支持多语言的RPC框架,如果单语言例如都是java,就直接dubbo只支持java。
比如有没有服务发现,服务监控,一个拥有服务治理的RPC框架,一般支持集群的部署和服务高可用。
目前流程RPC框架有哪些
2014年10月份,dubbo就不在维护了,时隔3年dubbo又重新开始维护,一来用户量确实很多,二来微服务比较火,对微服务更好的支持。DubboX是在阿里的dubbo基础上开发的一套DubboX。只支持java语言。
一套新浪微博的,2016年5月进行的开源,号称每天支持新浪微博的千亿级别的调用量,通过spring的调用方式不需要额外的代码就具有分布式的能力。只支持java语言。
2007年facebook开发的,08年进入了apche项目,它是一个跨语言的。毕竟那么多年,你想到的它都支持。没有服务治理相关的东西。
google开源的一个项目,跟Thrift相似,也支持跨语言。
对比
PS:微服务通信的根本就是RPC通信,比http效率高,稳定性好。
2-3 服务发现、部署更新和扩容
所有的表现形式都是ip+端口的形式。
服务比较少的话,可以通过下面的方式。如果服务很多的话,基本运维人员都崩溃死了。
服务太多的话,需要一种服务发现的机制。
适合小项目,服务少,服务器少。
服务数据居多,更新上线频繁。微服务如何解决这些问题呢
什么是服务排版,服务的发现,服务部署,服务更新,扩容,简化。
可以解决微服务遇到本节问题的解决
PS:抛出微服务的解决方案了,之后继续学习吧。
2-4 springboot&springcloud(上)
2-5 springboot&springcloud(下)
介绍了很多关于微服务的东西,大家对微服务有了一些认识,但是考虑到各位老铁java比较多,那就不得不说springboot 和 springcloud。在java的世界里他们跟微服务有这密切的关系,刚接触springboot和springcloud的同学可能存在一种混乱,springboot跟微服务的关系,springcloud跟微服务的关系,springboot和springcloud他们之间的关联关系,帮大家沥青思路,撇清关系,不在背锅!
SpringBoot的使命
springBoot是spring旗下的项目,它具体为什么出现,他的使命是什么?最主要的就是化繁为简,让我们开发spring变的简单,各种xml的配置,各种bean,服务接口,实现,缓存,消息队列,里面没个3个以上的spring配置文件很难看出来你是spring的项目,有点麻烦配置文件太多了。
原来的需要一个web服务器,tomcat,代码发布到服务器的指定位置。
讲web服务器和应用的包打在一起,让我们不用关心细节一个命令就可以启动。
尽可能自动化的配置spring,这里面很多配置都是固定的,这里面通过start以帮助我们简化maven的配置。
SpringBoot与微服务的关系
Java的润滑剂,springboot开发微服务的润滑剂。springboot的简化,简化的开发,简化的配置,简化的部署。微服务的特征是轻量灵活,多变,数量多。他们的特征非常的搭配,使用springboot开发微服务正好应对的微服务的特征,springboot开发和部署的过程更加变快了,所以springboot可以更快,更容易开发出更多的服务。如果你是java语言,使用springboot开发微服务是没错的。其实springboot没太多特别,但是效率提高了,天下武功唯快不破!
SpringCloud的使命
简化java的分布式系统,当你将java应用部署到多台服务器的时候,提供分布式能力的时候,第一要遇到的问题就是web端的session共享,多个服务之前的负载均衡,在nginx通过轮训的方式访问不同的tomcat。单机的情况下直接通过ip或者端口就可以直接访问了,如果是分布式怎么办?我们要自己写一个具有容错能力和负载聚恒的客户端吧,还有分布式下事务管理怎么办,其实在springcloud简化类似我们之前的一些问题。
springCloud为开发者快速开发具有分布式能力的服务,统一的配置管理,服务的注册,服务的调用,服务的发现,调度器,负载均衡,全局锁,分布式session。
集合框架
spring boot 简化了java的开发,spring cloud简化了分布式系统的开发(分布式系统的基础设施的开发)
几家服务框架进行了组合,通过springboot的风格进行封装,基于springboot的一款开发工具。
boot 和cloud
cloud vs 微服务
提供多台机器,部署了spring cloud的应用,但是他们之间的运维spring cloud做不了的。
spring cloud的核心组件
服务发现组件
客户端负载均衡组件
调度器
服务网关
分布式配置
发现没有里面很多都是netfilx,netfilx 其实是个美国在线影视公司。说说他的历史吧,我学什么语言喜欢看看他的历史。很久很久以前,有一家公司叫Blockbuster,称霸租碟业许多年。某个叫Reed Hastings的哥们在那里租了个碟,结果由于超期归还被黑走“一大笔”逾期费(大概40美元),怒了。然后他忿忿地去健身,发觉健身房商业模式甚是美哉,不管你去得多还是少,会员费半毛钱也不能少交。很不巧,Hastings是一个动不动就要改变世界的软件工程师,想法来了就要干,更不巧的是他当时已经非常有钱。于是愤怒之余他创办了Netflix,也是做租碟生意,没有逾期费并且搞会员制。十三年后Netflix把Blockbuster干到了破产保护,大仇得报。这个故事告诉我们两个道理:
1.客户服务一定要做好,不该薅的羊毛就别死命薅,不然你就是逼羊为虎。
2.工程师惹不起。
介绍下spring cloud的组件
主要了解原理,不会深入介绍spring cloud。
PS:下面我们一步一步spring cloud+spring boot创建的微服务,部署在服务编排框架上。
3-1 微服务业务分析
从本节开始微服务的开发,说到开发有几个问题需要解决,首先要知道我们需要开发什么?什么样的业务场景,分析业务场景,有几个微服务,每个微服务需要完成什么样的功能,微服务之间的关系,之间的依赖关系,他们之间是如何通迅的,这些都了解的之后,我们就可以进入开发阶段了。
现在的登录系统一般都是单点登录,支持跨域,在去使用其他系统的时候就不需要登录了,最好是不要使用session,最好是无状态的,避免使用session。
上边这个不是大而全的系统,只是微服务的功能,老铁咱们的目的很明确是搞微服务,不是学web开发的,我们通过上边的几个功能上从0开始了解微服务,一行不拉的完成开发微服务,让大家去开发有个真切的体会的。
PS:接下来,老铁跟我一起完成微服务的建设和搭建过程。
3-2 Thirft安装和验证
从这节开始微服务的开发阶段,首选根据下面的图,选择一个模块开始微服务的开发,我的开发习惯的就是检一些对比人依赖少的进行开发,找到了『信息服务』对其他依赖最少的,就开始开发这个,之前也说过thrift主要说的都是理论,这次咱们直接实战,先通过安装使用开始。源码:https://github.com/limingios/msA-docker
3-3 Python开发信息服务
namespace 编译的语言 包名
其实thrift的语言跟java语法很类似
- namespace java com.idig8.thrift.demo
- namespace py thrift.demo
-
- service DemoService{
-
- void sayIdig(1:string name);
-
- }
thrift-0.11.0.exe --gen java demo.thrift
thrift-0.11.0.exe --gen py demo.thrift
PS:我想开发一个快速计算的RPC服务,它主要通过接口函数getInt对外提供服务,这个RPC服务的getInt函数使用用户传入的参数,经过复杂的计算,计算出一个整形值返回给用户;服务器端使用java语言开发,而调用客户端可以是java、c、python等语言开发的程序,在这种应用场景下,我们只需要使用Thrift的IDL描述一下getInt函数(以.thrift为后缀的文件),然后使用Thrift的多语言编译功能,将这个IDL文件编译成C、java、python几种语言对应的“特定语言接口文件”(每种语言只需要一条简单的命令即可编译完成),这样拿到对应语言的“特定语言接口文件”之后,就可以开发客户端和服务器端的代码了,开发过程中只要接口不变,客户端和服务器端的开发可以独立的进行。
信息服务准备用python来写,在现有的idea中添加python的模块。源码:https://github.com/limingios/msA-docker
安装后重新idea。
开始我用idea写python,下载个插件都费劲,我换成了pycharm来写美滋滋
- # coding: utf-8
- from message.api import MessageService
- from thrift.transport import TSocket
- from thrift.transport import TTransport
- from thrift.protocol import TBinaryProtocol
- from thrift.server import TServer
-
- class MessageServiceHandler:
-
- def sendMobileMessage(self, mobile, message):
- print ("sendMobileMessage, mobile:"+mobile+", message:"+message)
- return True
-
- def sendEmailMessage(self, email, message):
- print ("sendEmailMessage, email:"+email+", message:"+message)
- return True
-
-
- if __name__ == '__main__':
- handler = MessageServiceHandler()
- processor = MessageService.Processor(handler)
- transport = TSocket.TServerSocket(None, "9090")
- tfactory = TTransport.TFramedTransportFactory()
- pfactory = TBinaryProtocol.TBinaryProtocolFactory()
-
- server = TServer.TSimpleServer(processor, transport, tfactory, pfactory)
- print ("python thrift server start")
- server.serve()
- print ("python thrift server exit")
都是根据thrift文件,生成对应的上级目录
- thrift --gen py -out ../ message.thrift
- thrift --gen java -out ../ message.thrift
PS:thrift的开发流程是: 先定义thrift的文件,然后通过命令生成对应的python代码。通过实现定义的thrift方法,来完成thrift的调用。
Thrift是一个跨语言的服务部署框架,最初由Facebook于2007年开发,2008年进入Apache开源项目。Thrift通过一个中间语言(IDL, 接口定义语言)来定义RPC的接口和数据类型,然后通过一个编译器生成不同语言的代码(目前支持C++,Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, Smalltalk和OCaml),并由生成的代码负责RPC协议层和传输层的实现。 Thrift实际上是实现了C/S模式,通过代码生成工具将接口定义文件生成服务器端和客户端代码(可以为不同语言),从而实现服务端和客户端跨语言的支持。用户在Thirft描述文件中声明自己的服务,这些服务经过编译后会生成相应语言的代码文件,然后用户实现服务(客户端调用服务,服务器端提服务)便可以了。其中protocol(协议层, 定义数据传输格式,可以为二进制或者XML等)和transport(传输层,定义数据传输方式,可以为TCP/IP传输,内存共享或者文件共享等)被用作运行时库。
3-4 开发用户服务(上)
3-5 开发用户服务(下)
这节咱们开始开发用户服务,上次通过python开发的信息服务已经开发完毕。源码:https://github.com/limingios/msA-docker
用户服务使用java语言进行开发,对外通过thift的接口,依赖于下面的信息服务,后端有数据库,开发一个服务,首选需要设计对外的接口,都给别人提供什么样的服务。
- <?xml version="1.0" encoding="UTF-8"?>
- <project xmlns="http://maven.apache.org/POM/4.0.0"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
- <parent>
- <artifactId>ms-server</artifactId>
- <groupId>com.idig8</groupId>
- <version>1.0-SNAPSHOT</version>
- </parent>
- <modelVersion>4.0.0</modelVersion>
-
- <groupId>com.idig8</groupId>
- <artifactId>user-thrift-service-api</artifactId>
- <version>1.0-SNAPSHOT</version>
-
- <dependencies>
- <dependency>
- <groupId>org.apache.thrift</groupId>
- <artifactId>libthrift</artifactId>
- <version>0.10.0</version>
- </dependency>
-
- </dependencies>
-
- <build>
- <plugins>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-compiler-plugin</artifactId>
- <version>2.3.2</version>
- <configuration>
- <source>1.8</source>
- <target>1.8</target>
- </configuration>
- </plugin>
- </plugins>
- </build>
-
- </project>
- namespace java com.idig8.thrift.user
- struct UserInfo{
- 1:i32 id,
- 2:string username,
- 3:string password,
- 4:string realName,
- 5:string mobile,
- 6:string email
- }
- service UserService{
-
- UserInfo getUserById(1:i32 id)
-
- UserInfo getUserByName(1:string username);
-
- void regiserUser(1:UserInfo userInfo);
- }
thrift --gen java -out ../src/main/java user-service.thrift
没有建立环境变量,直接在目录下生成的,然后拷贝到对应的目录下。
因自动生成源码太多,直接看文章头的连接地址下载吧
- # mac下
- cur_dir = 'pwd'
- docker stop idig8-mysql
- docker rm idig8-mysql
- docker run --name idig8-mysql -v ${cur_dir}/conf:/etc/mysql/conf.d -v ${cur_dir}/data:/var/lib/mysql -p 3306:330
这里我直接使用公网的一个ip地址来远程访问,新建数据库表
这些都是springboot的基本操作,建议看我的源码吧。
PS:老铁可能感觉很乱,我把思路从头到尾说一下
3-6 开发用户EdgeService_A
3-7 开发用户EdgeService_B
3-8 开发用户EdgeService_C
3-9 开发用户EdgeService_D
上一节开发了用户服务,即将开发的是用户服务EdgeService,从这个调用关系,可以看到用户的EdgeService是一个服务的服务,首选调用用户服务,对用户信息基本的操作,调用信息服务实现发送短信,发送邮件,还需要实现登录和注册的功能,并且登录是一个单点登录需要支持其他的系统,支持课程的登录的EdgeService,对他的要求是无状态的,还需要集中式的缓存redis。这么多服务集中于一身说明它是一个非常复杂的服务,不过也没关系,我们从头到尾把他开发完成。源码:https://github.com/limingios/msA-docker
- <?xml version="1.0" encoding="UTF-8"?>
- <project xmlns="http://maven.apache.org/POM/4.0.0"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
- <parent>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-parent</artifactId>
- <version>1.5.3.RELEASE</version>
- </parent>
- <modelVersion>4.0.0</modelVersion>
-
- <groupId>com.idig8</groupId>
- <artifactId>user-edge-service</artifactId>
- <version>1.0-SNAPSHOT</version>
-
- <dependencies>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-web</artifactId>
- </dependency>
- <dependency>
- <groupId>org.apache.thrift</groupId>
- <artifactId>libthrift</artifactId>
- <version>0.10.0</version>
- </dependency>
- <dependency>
- <groupId>com.idig8</groupId>
- <artifactId>user-thrift-service-api</artifactId>
- <version>1.0-SNAPSHOT</version>
- </dependency>
-
- <dependency>
- <groupId>com.idig8</groupId>
- <artifactId>message-thrift-service-api</artifactId>
- <version>1.0-SNAPSHOT</version>
- </dependency>
-
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-data-redis</artifactId>
- </dependency>
- <dependency>
- <groupId>commons-lang</groupId>
- <artifactId>commons-lang</artifactId>
- <version>2.6</version>
- </dependency>
-
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-thymeleaf</artifactId>
- </dependency>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot</artifactId>
- <version>RELEASE</version>
- <scope>compile</scope>
- </dependency>
-
-
- </dependencies>
-
- </project>
- package com.idig8.user.redis;
-
- import org.springframework.beans.factory.annotation.Value;
- import org.springframework.cache.CacheManager;
- import org.springframework.cache.annotation.CachingConfigurerSupport;
- import org.springframework.cache.annotation.EnableCaching;
- import org.springframework.context.annotation.Bean;
- import org.springframework.context.annotation.Configuration;
- import org.springframework.data.redis.cache.RedisCacheManager;
- import org.springframework.data.redis.connection.RedisConnectionFactory;
- import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
- import org.springframework.data.redis.core.RedisTemplate;
- import org.springframework.data.redis.core.StringRedisTemplate;
- import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
-
- import com.fasterxml.jackson.annotation.JsonAutoDetect;
- import com.fasterxml.jackson.annotation.PropertyAccessor;
- import com.fasterxml.jackson.databind.ObjectMapper;
-
-
- /**
- * Redis缓存配置类
- */
- @Configuration
- @EnableCaching
- public class RedisConfig extends CachingConfigurerSupport {
-
- @Value("${spring.redis.host}")
- private String host;
- @Value("${spring.redis.port}")
- private int port;
- @Value("${spring.redis.timeout}")
- private int timeout;
- @Value("${spring.redis.password}")
- private String password;
-
- //缓存管理器
- @Bean
- public CacheManager cacheManager(RedisTemplate redisTemplate) {
- RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate);
- //设置缓存过期时间
- cacheManager.setDefaultExpiration(10000);
- return cacheManager;
- }
-
- @Bean
- public JedisConnectionFactory redisConnectionFactory() {
- JedisConnectionFactory factory = new JedisConnectionFactory();
- factory.setHostName(host);
- factory.setPort(port);
- factory.setTimeout(timeout);
- factory.setPassword(password);
- return factory;
- }
-
- @Bean
- public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory){
- StringRedisTemplate template = new StringRedisTemplate(factory);
- setSerializer(template);//设置序列化工具
- template.afterPropertiesSet();
- return template;
- }
-
- private void setSerializer(StringRedisTemplate template){
- Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
- ObjectMapper om = new ObjectMapper();
- om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
- om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
- jackson2JsonRedisSerializer.setObjectMapper(om);
- template.setValueSerializer(jackson2JsonRedisSerializer);
- }
- }
-
RedisClient
- package com.idig8.user.redis;
-
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.data.redis.core.RedisTemplate;
- import org.springframework.stereotype.Component;
-
- import java.util.concurrent.TimeUnit;
-
- /**
- * Created by liming
- */
- @Component
- public class RedisClient {
-
- @Autowired
- private RedisTemplate redisTemplate;
-
- public <T> T get(String key) {
- return (T)redisTemplate.opsForValue().get(key);
- }
-
- public void set(String key, Object value) {
- redisTemplate.opsForValue().set(key, value);
- }
-
- public void set(String key, Object value, int timeout) {
- redisTemplate.opsForValue().set(key, value, timeout, TimeUnit.SECONDS);
- }
-
- public void expire(String key, int timeout) {
- redisTemplate.expire(key, timeout, TimeUnit.SECONDS);
- }
-
- }
Response
- package com.idig8.user.response;
-
- import java.io.Serializable;
-
- /**
- * Created by liming
- */
- public class Response implements Serializable {
-
- public static final Response USERNAME_PASSWORD_INVALID = new Response("1001", "username or password invalid");
-
- public static final Response MOBILE_OR_EMAIL_REQUIRED = new Response("1002", "mobile or email is required");
-
- public static final Response SEND_VERIFYCODE_FAILED = new Response("1003", "send verify code failed");
-
- public static final Response VERIFY_CODE_INVALID = new Response("1004", "verifyCode invalid");
-
-
- public static final Response SUCCESS = new Response();
-
-
-
- private String code;
- private String message;
-
- public Response() {
- this.code = "0";
- this.message = "success";
- }
- public Response(String code, String message) {
- this.code = code;
- this.message = message;
- }
-
- public static Response exception(Exception e) {
- return new Response("9999", e.getMessage());
- }
-
- public String getCode() {
- return code;
- }
-
- public void setCode(String code) {
- this.code = code;
- }
-
- public String getMessage() {
- return message;
- }
-
- public void setMessage(String message) {
- this.message = message;
- }
- }
-
LoginResponse
- package com.idig8.user.response;
-
- /**
- * Created by liming
- */
- public class LoginResponse extends Response {
-
- private String token;
-
- public LoginResponse(String token) {
- this.token = token;
- }
-
- public String getToken() {
- return token;
- }
-
- public void setToken(String token) {
- this.token = token;
- }
- }
- package com.idig8.user.thrift;
-
- import com.idig8.thrift.message.MessageService;
- import com.idig8.thrift.user.UserService;
- import org.apache.thrift.TServiceClient;
- import org.apache.thrift.protocol.TBinaryProtocol;
- import org.apache.thrift.protocol.TProtocol;
- import org.apache.thrift.transport.*;
- import org.springframework.beans.factory.annotation.Value;
- import org.springframework.stereotype.Component;
-
- @Component
- public class ServiceProvider {
-
- @Value("${thrift.user.ip}")
- private String serverIp;
-
- @Value("${thrift.user.port}")
- private int serverPort;
-
- @Value("${thrift.message.ip}")
- private String messageServerIp;
-
- @Value("${thrift.message.port}")
- private int messageServerPort;
-
- private enum ServiceType {
- USER,
- MESSAGE
- }
-
- public UserService.Client getUserService() {
-
- return getService(serverIp, serverPort, ServiceType.USER);
- }
-
- public MessageService.Client getMessasgeService() {
-
- return getService(messageServerIp, messageServerPort, ServiceType.MESSAGE);
- }
-
- public <T> T getService(String ip, int port, ServiceType serviceType) {
- TSocket socket = new TSocket(ip, port, 3000);
- TTransport transport = new TFramedTransport(socket);
- try {
- transport.open();
- } catch (TTransportException e) {
- e.printStackTrace();
- return null;
- }
- TProtocol protocol = new TBinaryProtocol(transport);
-
- TServiceClient result = null;
- switch (serviceType) {
- case USER:
- result = new UserService.Client(protocol);
- break;
- case MESSAGE:
- result = new MessageService.Client(protocol);
- break;
- }
- return (T)result;
- }
-
- }
因为userInfo是通过thrift自动升成的,里面很多方法太过麻烦,不利于开发查看数据内容,最好的方式自己创建一个对象,将自动升成userInfo转换成自定义的UserDTO,USerDTO最好的方式是在thrift的工程中进行的。如果多个项目,比较方便,单独的用户的edgeservice中进行DTO的话,只能他自己用业务不清晰。
- package com.idig8.user.controller;
-
- import com.idig8.thrift.user.UserInfo;
- import com.idig8.thrift.user.dto.UserDTO;
- import com.idig8.user.redis.RedisClient;
- import com.idig8.user.response.LoginResponse;
- import com.idig8.user.response.Response;
- import com.idig8.user.thrift.ServiceProvider;
- import org.apache.commons.lang.StringUtils;
- import org.apache.thrift.TException;
- import org.apache.tomcat.util.buf.HexUtils;
- import org.springframework.beans.BeanUtils;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.stereotype.Controller;
- import org.springframework.web.bind.annotation.RequestMapping;
- import org.springframework.web.bind.annotation.RequestMethod;
- import org.springframework.web.bind.annotation.RequestParam;
- import org.springframework.web.bind.annotation.ResponseBody;
-
- import java.security.MessageDigest;
- import java.util.Random;
-
- @Controller
- public class UserController {
-
-
- @Autowired
- private ServiceProvider serviceProvider;
-
- @Autowired
- private RedisClient redisClient;
-
- @RequestMapping(value = "/login",method = RequestMethod.POST)
- @ResponseBody
- public Response login(@RequestParam("username")String username,
- @RequestParam("password")String password){
- // 1. 验证用户名密码
- UserInfo userInfo = null;
- try {
- userInfo = serviceProvider.getUserService().getUserByName(username);
- } catch (TException e) {
- e.printStackTrace();
- return Response.USERNAME_PASSWORD_INVALID;
- }
- if (userInfo == null){
- return Response.USERNAME_PASSWORD_INVALID;
- }
- if(!userInfo.getPassword().equalsIgnoreCase(md5(password))){
- return Response.USERNAME_PASSWORD_INVALID;
- }
-
- // 2. 生成token
- String token = genToken();
- // 3. 缓存用户
-
- //因为userInfo是通过thrift自动升成的,里面很多方法太过麻烦,不利于开发查看数据内容
- //最好的方式自己创建一个对象,将自动升成userInfo转换成自定义的UserDTO
- redisClient.set(token,toDTO(userInfo));
-
- return new LoginResponse(token);
-
- }
-
- @RequestMapping(value = "/sendVerifyCode", method = RequestMethod.POST)
- @ResponseBody
- public Response sendVerifyCode(@RequestParam(value="mobile", required = false) String mobile,
- @RequestParam(value="email", required = false) String email) {
-
- String message = "Verify code is:";
- String code = randomCode("0123456789", 6);
- try {
-
- boolean result = false;
- if(StringUtils.isNotBlank(mobile)) {
- result = serviceProvider.getMessasgeService().sendMobileMessage(mobile, message+code);
- redisClient.set(mobile, code);
- } else if(StringUtils.isNotBlank(email)) {
- result = serviceProvider.getMessasgeService().sendEmailMessage(email, message+code);
- redisClient.set(email, code);
- } else {
- return Response.MOBILE_OR_EMAIL_REQUIRED;
- }
-
- if(!result) {
- return Response.SEND_VERIFYCODE_FAILED;
- }
- } catch (TException e) {
- e.printStackTrace();
- return Response.exception(e);
- }
-
- return Response.SUCCESS;
-
- }
-
- @RequestMapping(value="/register", method = RequestMethod.POST)
- @ResponseBody
- public Response register(@RequestParam("username") String username,
- @RequestParam("password") String password,
- @RequestParam(value="mobile", required = false) String mobile,
- @RequestParam(value="email", required = false) String email,
- @RequestParam("verifyCode") String verifyCode) {
-
- if(StringUtils.isBlank(mobile) && StringUtils.isBlank(email)) {
- return Response.MOBILE_OR_EMAIL_REQUIRED;
- }
-
- if(StringUtils.isNotBlank(mobile)) {
- String redisCode = redisClient.get(mobile);
- if(!verifyCode.equals(redisCode)) {
- return Response.VERIFY_CODE_INVALID;
- }
- }else {
- String redisCode = redisClient.get(email);
- if(!verifyCode.equals(redisCode)) {
- return Response.VERIFY_CODE_INVALID;
- }
- }
- UserInfo userInfo = new UserInfo();
- userInfo.setUsername(username);
- userInfo.setPassword(md5(password));
- userInfo.setMobile(mobile);
- userInfo.setEmail(email);
-
- try {
- serviceProvider.getUserService().regiserUser(userInfo);
- } catch (TException e) {
- e.printStackTrace();
- return Response.exception(e);
- }
-
- return Response.SUCCESS;
- }
-
- private UserDTO toDTO(UserInfo userInfo) {
- UserDTO userDTO = new UserDTO();
- BeanUtils.copyProperties(userInfo, userDTO);
- return userDTO;
- }
-
- private String genToken() {
- return randomCode("0123456789abcdefghijklmnopqrstuvwxyz", 32);
- }
-
- private String randomCode(String s, int size) {
- StringBuilder result = new StringBuilder(size);
-
- Random random = new Random();
- for(int i=0;i<size;i++) {
- int loc = random.nextInt(s.length());
- result.append(s.charAt(loc));
- }
- return result.toString();
- }
-
- private String md5(String password) {
- try {
- MessageDigest md5 = MessageDigest.getInstance("MD5");
- byte[] md5Bytes = md5.digest(password.getBytes("utf-8"));
- return HexUtils.toHexString(md5Bytes);
- } catch (Exception e) {
- e.printStackTrace();
- }
- return null;
- }
- }
- package com.idig8.user.client;
-
- import com.google.common.cache.Cache;
- import com.google.common.cache.CacheBuilder;
- import com.idig8.thrift.user.dto.UserDTO;
- import org.apache.commons.lang.StringUtils;
- import org.apache.http.HttpResponse;
- import org.apache.http.HttpStatus;
- import org.apache.http.client.HttpClient;
- import org.apache.http.client.methods.HttpPost;
- import org.apache.http.impl.client.DefaultHttpClient;
- import org.codehaus.jackson.map.ObjectMapper;
-
- import javax.servlet.Filter;
- import javax.servlet.FilterChain;
- import javax.servlet.FilterConfig;
- import javax.servlet.ServletException;
- import javax.servlet.ServletRequest;
- import javax.servlet.ServletResponse;
- import javax.servlet.http.Cookie;
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletResponse;
- import java.io.IOException;
- import java.io.InputStream;
- import java.util.concurrent.TimeUnit;
-
- /**
- * Created by Michael on 2017/10/31.
- */
- public abstract class LoginFilter implements Filter {
-
- private static Cache<String, UserDTO> cache =
- CacheBuilder.newBuilder().maximumSize(10000)
- .expireAfterWrite(3, TimeUnit.MINUTES).build();
-
-
- public void init(FilterConfig filterConfig) throws ServletException {
-
- }
-
- public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
-
- HttpServletRequest request = (HttpServletRequest)servletRequest;
- HttpServletResponse response = (HttpServletResponse)servletResponse;
-
- String token = request.getParameter("token");
- if(StringUtils.isBlank(token)) {
- Cookie[] cookies = request.getCookies();
- if(cookies!=null) {
- for(Cookie c : cookies) {
- if(c.getName().equals("token")) {
- token = c.getValue();
- }
- }
- }
- }
-
- UserDTO userDTO = null;
- if(StringUtils.isNotBlank(token)) {
- userDTO = cache.getIfPresent(token);
- if(userDTO==null) {
- userDTO = requestUserInfo(token);
- if(userDTO!=null) {
- cache.put(token, userDTO);
- }
- }
- }
-
- if(userDTO==null) {
- response.sendRedirect("http://www.mooc.com/user/login");
- return;
- }
-
- login(request, response, userDTO);
-
- filterChain.doFilter(request, response);
- }
-
- protected abstract String userEdgeServiceAddr();
-
- protected abstract void login(HttpServletRequest request, HttpServletResponse response, UserDTO userDTO);
-
- private UserDTO requestUserInfo(String token) {
- String url = "http://"+userEdgeServiceAddr()+"/user/authentication";
-
- HttpClient client = new DefaultHttpClient();
- HttpPost post = new HttpPost(url);
- post.addHeader("token", token);
- InputStream inputStream = null;
- try {
- HttpResponse response = client.execute(post);
- if(response.getStatusLine().getStatusCode()!= HttpStatus.SC_OK) {
- throw new RuntimeException("request user info failed! StatusLine:"+response.getStatusLine());
- }
- inputStream = response.getEntity().getContent();
- byte[] temp = new byte[1024];
- StringBuilder sb = new StringBuilder();
- int len = 0;
- while((len = inputStream.read(temp))>0) {
- sb.append(new String(temp,0,len));
- }
-
- UserDTO userDTO = new ObjectMapper().readValue(sb.toString(), UserDTO.class);
- return userDTO;
- } catch (IOException e) {
- e.printStackTrace();
- } finally {
- if(inputStream!=null) {
- try{
- inputStream.close();
- }catch(Exception e) {
- e.printStackTrace();
- }
- }
- }
- return null;
- }
-
- public void destroy() {
-
- }
- }
需要引入单点登录的模块进行实现的功能
PS:其实通过梳理发现这个还是有套路可寻的如何多语言进行通信,先生成对应的语言的代码,然后通过rpc的服务端和客户端,他们之前进行协议话的通信,服务端完成自身的业务逻辑,客户端就获取返回的结果。
3-10 dubbo入门操练(上)
3-11 dubbo入门操练(下)
接下来我们即将开始说课程管理,课程服务他是基于dubbo实现的,所以先来预热下,dubbo,对不熟悉的dubbo的老铁进行一下讲解。 源码:https://github.com/limingios/dubbo.git
高性能的基于java的,RPC框架。dubbo是阿里巴巴开源的一个项目,就像大多的RPC框架,dubbo的思想围绕一个服务,指定一个方法的参数和返回值,这个方法可以被远程调用的,在服务端,会实现这个接口,会运行dubbo的服务用来处理客户端的调用,在客户端,会有一个存根它提供和服务端想通的方法。其实这些概念用java的术语:首先要定义一个接口,这个接口在服务端和客户端公用,服务端会完成这个接口的实现,客户端通过接口的描述来调用服务端。
创建三个api,provider,consumer 三个项目
- <?xml version="1.0" encoding="UTF-8"?>
- <beans xmlns="http://www.springframework.org/schema/beans"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
- xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
-
- <!-- 提供方应用信息,用于计算依赖关系 -->
- <dubbo:application name="demo-provider" />
-
- <!-- 使用multicast广播注册中心暴露服务地址 -->
- <dubbo:registry register="false" />
-
- <!-- 用dubbo协议在20880端口暴露服务 -->
- <dubbo:protocol name="dubbo" port="20880" />
-
- <!-- 声明需要暴露的服务接口 -->
- <dubbo:service interface="com.alibaba.dubbo.demo.DemoService" ref="demoService" />
-
- <!-- 和本地bean一样实现服务 -->
- <bean id="demoService" class="com.alibaba.dubbo.demo.provider.DemoServiceImpl" />
- </beans>
consumer.xml
- <?xml version="1.0" encoding="UTF-8"?>
- <project xmlns="http://maven.apache.org/POM/4.0.0"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
- <parent>
- <artifactId>dubbo-demo</artifactId>
- <groupId>com.idig8</groupId>
- <version>1.0-SNAPSHOT</version>
- </parent>
- <modelVersion>4.0.0</modelVersion>
-
- <artifactId>dubbo-demo-consumer</artifactId>
- <dependencies>
- <dependency>
- <groupId>com.idig8</groupId>
- <artifactId>dubbo-demo-api</artifactId>
- <version>1.0-SNAPSHOT</version>
- </dependency>
- <dependency>
- <groupId>com.alibaba</groupId>
- <artifactId>dubbo</artifactId>
- <version>2.6.4</version>
- </dependency>
- <dependency>
- <groupId>org.springframework</groupId>
- <artifactId>spring-context</artifactId>
- <version>4.3.0.RELEASE</version>
- </dependency>
- </dependencies>
-
- </project>
- package com.idig8.springboot.dubbo.demo;
-
- public interface DemoService {
- String sayHello(String name);
- }
对外提供的maven坐标如下:
- <dependency>
- <groupId>com.idig8</groupId>
- <artifactId>dubbo-demo-api</artifactId>
- <version>1.0-SNAPSHOT</version>
- </dependency>
- <!-- springboot parent -->
- <parent>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-parent</artifactId>
- <version>1.5.3.RELEASE</version>
- </parent>
- <!-- springboot dubbo starter -->
- <dependency>
- <groupId>io.dubbo.springboot</groupId>
- <artifactId>spring-boot-starter-dubbo</artifactId>
- <version>1.0.0</version>
- </dependency>
- <!-- api dependence -->
- <dependency>
- <groupId>com.idig8</groupId>
- <artifactId>dubbo-demo-api</artifactId>
- <version>1.0-SNAPSHOT</version>
- </dependency>
- package com.idig8.springboot.dubbo.demo.provider;
-
- import com.alibaba.dubbo.config.annotation.Service;
- import com.idig8.springboot.dubbo.demo.DemoService;
-
- @Service
- public class DemoServiceImpl implements DemoService {
-
- public String sayHello(String name) {
- return "Hello, " + name + " (from Spring Boot)";
- }
-
- }
- spring.dubbo.application.name=demo-provider
- #这里使用广播的注册方式,
- #如果有Can't assign address异常需要加vm参数:
- #-Djava.net.preferIPv4Stack=true
- spring.dubbo.registry.address=multicast://224.5.6.7:1234
- spring.dubbo.protocol.name=dubbo
- spring.dubbo.protocol.port=20880
- spring.dubbo.scan=com.idig8.springboot.dubbo.demo.provider
- package com.idig8.springboot.dubbo.demo.provider;
-
- import org.springframework.boot.SpringApplication;
- import org.springframework.boot.autoconfigure.SpringBootApplication;
-
- @SpringBootApplication
- public class MainProvider {
- public static void main(String[] args) {
- SpringApplication.run(MainProvider.class,args);
- }
- }
- <!-- springboot parent -->
- <parent>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-parent</artifactId>
- <version>1.5.3.RELEASE</version>
- </parent>
- <!-- springboot web starter -->
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-web</artifactId>
- </dependency>
- <!-- springboot dubbo starter -->
- <dependency>
- <groupId>io.dubbo.springboot</groupId>
- <artifactId>spring-boot-starter-dubbo</artifactId>
- <version>1.0.0</version>
- </dependency>
- <!-- api dependency -->
- <dependency>
- <groupId>com.idig8</groupId>
- <artifactId>dubbo-demo-api</artifactId>
- <version>1.0-SNAPSHOT</version>
- </dependency>
- package com.idig8.springboot.dubbo.demo.consumer;
-
- import com.alibaba.dubbo.config.annotation.Reference;
- import com.idig8.springboot.dubbo.demo.DemoService;
- import org.springframework.web.bind.annotation.RequestMapping;
- import org.springframework.web.bind.annotation.RequestParam;
- import org.springframework.web.bind.annotation.RestController;
-
- @RestController
- public class DemoConsumerController {
-
- @Reference
- private DemoService demoService;
-
- @RequestMapping("/sayHello")
- public String sayHello(@RequestParam String name) {
- return demoService.sayHello(name);
- }
-
- }
- server.port=8080
- #dubbo config
- spring.dubbo.application.name=demo-consumer
- #这里使用广播的注册方式,
- #如果有Can't assign address异常需要加vm参数:
- #-Djava.net.preferIPv4Stack=true
- spring.dubbo.registry.address=multicast://224.5.6.7:1234
- spring.dubbo.scan=com.idig8.springboot.dubbo.demo.consumer
- package com.idig8.springboot.dubbo.demo.consumer;
-
- import org.springframework.boot.SpringApplication;
- import org.springframework.boot.autoconfigure.SpringBootApplication;
-
- @SpringBootApplication
- public class Main {
-
- public static void main(String[] args) {
- SpringApplication.run(Main.class,args);
- }
-
- }
PS:dubbo的入门也就到这里,从spring 和springboot 对dubbo的整合。
流程基本之前也说,api 建立接口,provider 实现接口, consumer 调用接口。
3-12 开发课程服务
这次一起编写课程服务。之前的用户服务,用户EdgeSerivce,用户信息,都已经开发完毕了,开始开发课程服务,对外的是dubbo接口,需要访问后端的数据库。源码:https://github.com/limingios/msA-docker
基于dubbo的服务,一般先定义api接口,前面的都是基于thrift的,我们先写一个thrift的文件,然后根据配置文件升成对应的api,dubbo我们相当于先手写一个api的模块。
就不在复制代码了只通过截图来讲述功能,可以参考源码,本次跟用到了上次springboot集成dubbo的方式。
- <?xml version="1.0" encoding="UTF-8"?>
- <project xmlns="http://maven.apache.org/POM/4.0.0"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
- <parent>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-parent</artifactId>
- <version>1.5.3.RELEASE</version>
- </parent>
- <modelVersion>4.0.0</modelVersion>
-
- <groupId>com.idig8</groupId>
- <artifactId>course-dubbo-service</artifactId>
- <version>1.0-SNAPSHOT</version>
-
- <dependencies>
- <dependency>
- <groupId>io.dubbo.springboot</groupId>
- <artifactId>spring-boot-starter-dubbo</artifactId>
- <version>1.0.0</version>
- </dependency>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter</artifactId>
- </dependency>
- <dependency>
- <groupId>org.apache.thrift</groupId>
- <artifactId>libthrift</artifactId>
- <version>0.10.0</version>
- </dependency>
-
- <dependency>
- <groupId>org.mybatis.spring.boot</groupId>
- <artifactId>mybatis-spring-boot-starter</artifactId>
- <version>1.3.1</version>
- </dependency>
-
- <dependency>
- <groupId>mysql</groupId>
- <artifactId>mysql-connector-java</artifactId>
- <version>5.1.44</version>
- </dependency>
- <dependency>
- <groupId>com.idig8</groupId>
- <artifactId>course-dubbo-service-api</artifactId>
- <version>1.0-SNAPSHOT</version>
- </dependency>
-
- <dependency>
- <groupId>com.idig8</groupId>
- <artifactId>user-thrift-service</artifactId>
- <version>1.0-SNAPSHOT</version>
- </dependency>
- <dependency>
- <groupId>com.idig8</groupId>
- <artifactId>user-thrift-service-api</artifactId>
- <version>1.0-SNAPSHOT</version>
- </dependency>
- </dependencies>
-
- </project>
PS:基本的课程开发思路就是这样,别人有的微服务调用,只实现自己所属的。
3-13 开发课程EdgeService
课程的edgeService依赖于课程服务的dubbo服务,对外提供的restAPI,跟用户的EdgeService有点类似,只是一个调用的是thrift,一个调用的是dubbo,比较特殊的是课程的EdgeService需要用户登录后才可以访问,如果没有登录的话,需要跳转到登录系统才可以访问。源码:https://github.com/limingios/msA-docker
- <?xml version="1.0" encoding="UTF-8"?>
- <project xmlns="http://maven.apache.org/POM/4.0.0"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
- <parent>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-parent</artifactId>
- <version>1.5.3.RELEASE</version>
- </parent>
- <modelVersion>4.0.0</modelVersion>
-
- <groupId>com.idig8</groupId>
- <artifactId>course-edge-service</artifactId>
- <version>1.0-SNAPSHOT</version>
-
- <dependencies>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-web</artifactId>
- </dependency>
- <dependency>
- <groupId>commons-lang</groupId>
- <artifactId>commons-lang</artifactId>
- <version>2.6</version>
- </dependency>
- <dependency>
- <groupId>io.dubbo.springboot</groupId>
- <artifactId>spring-boot-starter-dubbo</artifactId>
- <version>1.0.0</version>
- </dependency>
- <dependency>
- <groupId>com.idig8</groupId>
- <artifactId>course-dubbo-service-api</artifactId>
- <version>1.0-SNAPSHOT</version>
- </dependency>
- <dependency>
- <groupId>com.idig8</groupId>
- <artifactId>user-edge-service-client</artifactId>
- <version>1.0-SNAPSHOT</version>
- </dependency>
-
- </dependencies>
-
- </project>
原创文章,欢迎转载。转载请注明:转载自IT人故事会,谢谢!
原文链接地址:『高级篇』docker之开发课程EdgeService(16)
课程的edgeService依赖于课程服务的dubbo服务,对外提供的restAPI,跟用户的EdgeService有点类似,只是一个调用的是thrift,一个调用的是dubbo,比较特殊的是课程的EdgeService需要用户登录后才可以访问,如果没有登录的话,需要跳转到登录系统才可以访问。源码:https://github.com/limingios/msA-docker
- <?xml version="1.0" encoding="UTF-8"?>
- <project xmlns="http://maven.apache.org/POM/4.0.0"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
- <parent>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-parent</artifactId>
- <version>1.5.3.RELEASE</version>
- </parent>
- <modelVersion>4.0.0</modelVersion>
-
- <groupId>com.idig8</groupId>
- <artifactId>course-edge-service</artifactId>
- <version>1.0-SNAPSHOT</version>
-
- <dependencies>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-web</artifactId>
- </dependency>
- <dependency>
- <groupId>commons-lang</groupId>
- <artifactId>commons-lang</artifactId>
- <version>2.6</version>
- </dependency>
- <dependency>
- <groupId>io.dubbo.springboot</groupId>
- <artifactId>spring-boot-starter-dubbo</artifactId>
- <version>1.0.0</version>
- </dependency>
- <dependency>
- <groupId>com.idig8</groupId>
- <artifactId>course-dubbo-service-api</artifactId>
- <version>1.0-SNAPSHOT</version>
- </dependency>
- <dependency>
- <groupId>com.idig8</groupId>
- <artifactId>user-edge-service-client</artifactId>
- <version>1.0-SNAPSHOT</version>
- </dependency>
-
- </dependencies>
-
- </project>
- package com.idig8.course.controller;
-
- import com.alibaba.dubbo.config.annotation.Reference;
- import com.idig8.course.dto.CourseDTO;
- import com.idig8.course.service.ICourseService;
- import com.idig8.thrift.user.dto.UserDTO;
- import org.springframework.stereotype.Controller;
- import org.springframework.web.bind.annotation.RequestMapping;
- import org.springframework.web.bind.annotation.RequestMethod;
- import org.springframework.web.bind.annotation.ResponseBody;
-
- import javax.servlet.http.HttpServletRequest;
- import java.util.List;
-
- /**
- * Created by Michael on 2017/11/4.
- */
- @Controller
- @RequestMapping("/course")
- public class CourseController {
-
- @Reference
- private ICourseService courseService;
-
- @RequestMapping(value = "/courseList", method = RequestMethod.GET)
- @ResponseBody
- public List<CourseDTO> courseList(HttpServletRequest request) {
-
- UserDTO user = (UserDTO)request.getAttribute("user");
- System.out.println(user.toString());
-
- return courseService.courseList();
- }
- }
- package com.idig8.course.filter;
-
- import com.idig8.thrift.user.dto.UserDTO;
- import com.idig8.user.client.LoginFilter;
- import org.springframework.beans.factory.annotation.Value;
- import org.springframework.stereotype.Component;
-
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletResponse;
-
- /**
- * Created by liming.
- */
- @Component
- public class CourseFilter extends LoginFilter {
-
- @Value("${user.edge.service.addr}")
- private String userEdgeServiceAddr;
-
- @Override
- protected String userEdgeServiceAddr() {
- return userEdgeServiceAddr;
- }
-
- @Override
- protected void login(HttpServletRequest request, HttpServletResponse response, UserDTO userDTO) {
-
- request.setAttribute("user", userDTO);
- }
- }
- package com.idig8.course;
-
- import com.idig8.course.filter.CourseFilter;
- import org.springframework.boot.SpringApplication;
- import org.springframework.boot.autoconfigure.SpringBootApplication;
- import org.springframework.boot.web.servlet.FilterRegistrationBean;
- import org.springframework.context.annotation.Bean;
-
- import java.util.ArrayList;
- import java.util.List;
-
- /**
- * Created by liming
- */
- @SpringBootApplication
- public class ServiceApplication {
-
- public static void main(String args[]) {
- SpringApplication.run(ServiceApplication.class, args);
- }
-
- @Bean
- public FilterRegistrationBean filterRegistrationBean(CourseFilter courseFilter ) {
- FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
- filterRegistrationBean.setFilter(courseFilter);
-
- List<String> urlPatterns = new ArrayList<String>();
- urlPatterns.add("/*");
- filterRegistrationBean.setUrlPatterns(urlPatterns);
- return filterRegistrationBean;
- }
- }
- server.port=8081
-
- #dubbo config
- spring.dubbo.application.name=course-service
- spring.dubbo.registry.address=zookeeper://47.98.183.16:2181
- spring.dubbo.scan=com.idig8.course
-
- user.edge.service.addr=127.0.0.1:8082
PS:微服务跟之前说的一样就是互相通过RPC的方式进行通信,之间有自己的数据库,只是RPC暴露接口的方式来获取其他的微服务之间的数据。
3-14 APIGatewayZuul
这次说最后一个模块APIGateway,他的功能就是将我们客户端的请求统一的转发到用户和课程的EdgeService上面,ApiGetway我们使用springClud来实现。源码:https://github.com/limingios/msA-docker
- <?xml version="1.0" encoding="UTF-8"?>
- <project xmlns="http://maven.apache.org/POM/4.0.0"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
- <parent>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-parent</artifactId>
- <version>1.5.3.RELEASE</version>
- </parent>
- <modelVersion>4.0.0</modelVersion>
-
- <groupId>com.idig8</groupId>
- <artifactId>api-gateway-zuul</artifactId>
- <version>1.0-SNAPSHOT</version>
-
- <dependencies>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-web</artifactId>
- </dependency>
-
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-zuul</artifactId>
- <version>1.3.5.RELEASE</version>
- </dependency>
-
- </dependencies>
- </project>
- server.port=8080
-
- zuul.routes.course.path=/course/**
- zuul.routes.course.url=http://127.0.0.1:8081/course/
-
- zuul.routes.user.path=/user/**
- zuul.routes.user.url=http://127.0.0.1:8082/user/
- package com.idig8.zuul;
-
- import org.springframework.boot.SpringApplication;
- import org.springframework.boot.autoconfigure.SpringBootApplication;
- import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
-
- /**
- * Created by liming
- */
- @SpringBootApplication
- @EnableZuulProxy
- public class ServiceApplication {
-
- public static void main(String args[]) {
- SpringApplication.run(ServiceApplication.class, args);
- }
- }
所有的业务开发完毕,zuul 就是可以帮助我们做路由和转发的工作。所有的请求帮你做中转。虽然业务非常简单,但是框架都能实现。业务也不是这次学习微服务的重点。在整个开发过程中主要想让各位老铁体会到微服务,不同的RPC的通信方式,没有使用过dubbo和thrift的可以了解下如何的使用。还搞了夸语言的业务通信,用python写了一个message消息服务,用java做客户端调用python,这里面的java模块我们使用的springboot,来进入一个切入点,能感受到开发和配置模式的统一,最后我们使用zuul作为服务网关,完成了服务路由,可以注意到所有服务的开发都是使用了相对简单的模型和功能,并没有大而全的角色存在,因为这次主要就是说的针对不太了解微服务的老铁,只为你们打开一个微服务的大门。
PS:就像跟陌生人交朋友,不可能上来直接详细的自我介绍,一般都是先聊点其他的,或者从大家都感兴趣的一个话题作为切入点,一点点增加彼此的了解,其实学习也是一样的,一个新的功能一定会有新的功能和特性,我们必须从一个点入手,先用起来后,在一点点的了解,就像这个zuul,只用到他的服务路由,下次咱们就通过这些微服务为基础进行docker话,让老铁知道一个非docker的项目如何运行在一个docker上面。需要关心的点是什么上面。然后我们在本地吧这些服务都运行起来。最后我们把他交给服务编排框架,看它是怎么调度管理容器的。
第4章 服务编排前奏
4-1 服务docker化(上)
4-2 服务docker化(下)
这次进入微服务的部署,代码也基本都通过了。如果比做一首歌曲的话,前奏已经结束,现在开始我们的高潮部分,如果吧我们的服务使用docker,使用服务编排工具,把项目给部署运行起来。源码:https://github.com/limingios/msA-docker
因docker话都是在linux环境,为了方便编写dockerfile文件,我切换到mac本上进行演示,目的只有一个方便开发sh。方便使用。CICD学习实践首选电脑还是mac。
服务有个适合的环境,服务可以运行起来,给他准备一个环境,比如服务是个种子,我们需要准备一片土地,服务是一条鱼,就需要准备一片大海。源码中的服务有一个是python写的,有一个是java写的,也就是需要2个运行环境,一个基于java,一个基于python。
去hub.docker.com 搜索java,找到tag
先配置加速,道客镜像站
- docker pull java:openjdk-8
- docker images|grep jdk
- docker run -it --entrypoint bash java:openjdk-8
- java -version
熟悉docker的老铁应该都知道,如果要编译docker的话需要docker化,首选需要开发dockerfile文件。
文件中不能有写死的情况,如果写死了,每次服务的变更都需要变更镜像。为了减少构建镜像的过程,尽量吧数据库的访问地址,经常会发生变化的东西,需要踢出去,不要在配置文件中配置死,针对数据库的访问,不能地址直接写死,mysql的地址,当服务运行在docker之后,他的ip是实时都在变化的,不能写死在镜像里,直接就找不到了就报错了。还有个问题,我们的服务以什么样的形式放在我们docker里面,springboot之所以说适用于微服务,他有个很大的好处,它可以将咱们的服务构建成一个fat jar,只有一个jar包,然后通过java的一个命令:java -jar 文件.jar 运行起来,这种方式对于微服务来说也是很友好的,也非常的简单,就使用这种方式来做。变量的方式就可以通过springboot --mysql.address 就可以传递进来了。
修改配置文件 和 pom.xml文件
application.properties
- service.name=user-thrift-service
- service.port=7911
-
- #数据源的配置
- spring.datasource.url=jdbc:mysql://{mysql.address}:3306/db_user
- spring.datasource.username=root
- spring.datasource.password=root
- spring.datasource.driver-class-name=com.mysql.jdbc.Driver
-
pom.xml
- <?xml version="1.0" encoding="UTF-8"?>
- <project xmlns="http://maven.apache.org/POM/4.0.0"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
- <parent>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-parent</artifactId>
- <version>1.5.3.RELEASE</version>
- </parent>
- <modelVersion>4.0.0</modelVersion>
-
- <groupId>com.idig8</groupId>
- <artifactId>user-thrift-service</artifactId>
- <version>1.0-SNAPSHOT</version>
-
- <dependencies>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter</artifactId>
- </dependency>
- <dependency>
- <groupId>org.apache.thrift</groupId>
- <artifactId>libthrift</artifactId>
- <version>0.10.0</version>
- </dependency>
- <dependency>
- <groupId>com.idig8</groupId>
- <artifactId>user-thrift-service-api</artifactId>
- <version>1.0-SNAPSHOT</version>
- </dependency>
- <dependency>
- <groupId>org.mybatis.spring.boot</groupId>
- <artifactId>mybatis-spring-boot-starter</artifactId>
- <version>1.3.1</version>
- </dependency>
-
- <dependency>
- <groupId>mysql</groupId>
- <artifactId>mysql-connector-java</artifactId>
- <version>5.1.44</version>
- </dependency>
-
- </dependencies>
-
- <build>
- <plugins>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-compiler-plugin</artifactId>
- <version>2.3.2</version>
- <configuration>
- <source>1.8</source>
- <target>1.8</target>
- </configuration>
- </plugin>
- <plugin>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-maven-plugin</artifactId>
- <executions>
- <execution>
- <goals>
- <goal>repackage</goal>
- </goals>
- </execution>
- </executions>
- </plugin>
- </plugins>
- </build>
-
- </project>
Dockfile编写
- FROM java:openjdk-8
- MAINTAINER liming www.idig8.com
-
- COPY target/user-thrift-service-1.0-SNAPSHOT.jar /user-thrift-service.jar
-
- ENTRYPOINT ["java","-jar","/user-thrift-service.jar"]
执行build生成镜像
docker build -t user-thrift-service:latest .
查看ip地址
ifconfig
生成容器
docker run -it user-thrift-service:latest --mysql.address=192.168.1.140
- #!/usr/bin/env bash
-
- mvn package
- docker build -t user-thrift-service:latest .
后面很多的服务,都依赖它,必须把它做好。它是一个python的服务,我们需要找一个python的镜像。去官方找吧
- docker pull python:3.6
- docker images|grep python
编写Dockerfile
- FROM python:3.6
- MAINTAINER liming www.idig8.com
-
- RUN pip install thrift
-
- ENV PYTHONPATH /
- COPY message /message
- ENTRYPOINT ["python","/message/message_service.py"]
-
-
docker build -t message-thrift-python-service:latest .
build开发
- #!/usr/bin/env bash
-
- docker build -t message-thrift-python-service:latest .
镜像生成容器
docker run -it message-thrift-python-service:latest
微服务的依赖,了解docker的老铁应该知道可以通过link的方式,去根据名字搜索到这个服务,只要不在开发范围内的,认为是通过另外的介入方式ip,域名的方式。通过docker分为2种情况,微服务和微服务之间的通信,微服务和外围系统的通信。
修改配置文件 和 pom.xml文件
application.properties
pom.xml
- <?xml version="1.0" encoding="UTF-8"?>
- <project xmlns="http://maven.apache.org/POM/4.0.0"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
- <parent>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-parent</artifactId>
- <version>1.5.3.RELEASE</version>
- </parent>
- <modelVersion>4.0.0</modelVersion>
-
- <groupId>com.idig8</groupId>
- <artifactId>user-edge-service</artifactId>
- <version>1.0-SNAPSHOT</version>
-
- <dependencies>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-web</artifactId>
- </dependency>
- <dependency>
- <groupId>org.apache.thrift</groupId>
- <artifactId>libthrift</artifactId>
- <version>0.10.0</version>
- </dependency>
- <dependency>
- <groupId>com.idig8</groupId>
- <artifactId>user-thrift-service-api</artifactId>
- <version>1.0-SNAPSHOT</version>
- </dependency>
-
- <dependency>
- <groupId>com.idig8</groupId>
- <artifactId>message-thrift-service-api</artifactId>
- <version>1.0-SNAPSHOT</version>
- </dependency>
-
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-data-redis</artifactId>
- </dependency>
- <dependency>
- <groupId>commons-lang</groupId>
- <artifactId>commons-lang</artifactId>
- <version>2.6</version>
- </dependency>
-
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-thymeleaf</artifactId>
- </dependency>
-
-
-
- </dependencies>
-
- <build>
- <plugins>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-compiler-plugin</artifactId>
- <version>2.3.2</version>
- <configuration>
- <source>1.8</source>
- <target>1.8</target>
- </configuration>
- </plugin>
- <plugin>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-maven-plugin</artifactId>
- <executions>
- <execution>
- <goals>
- <goal>repackage</goal>
- </goals>
- </execution>
- </executions>
- </plugin>
- </plugins>
- </build>
-
- </project>
Dockerfile编写
- FROM java:openjdk-8
- MAINTAINER liming www.idig8.com
-
- COPY target/user-edge-service-1.0-SNAPSHOT.jar /user-edge-service.jar
-
- ENTRYPOINT ["java","-jar","/user-edge-service.jar"]
build.sh 编写
- #!/usr/bin/env bash
-
- mvn package
- docker build -t user-edge-service:latest .
sh build.sh
创建容器
docker run -it user-edge-service:latest --redis.address=192.168.1.140
微服务的依赖,了解docker的老铁应该知道可以通过link的方式,去根据名字搜索到这个服务,只要不在开发范围内的,认为是通过另外的介入方式ip,域名的方式。通过docker分为2种情况,微服务和微服务之间的通信,微服务和外围系统的通信。
修改配置文件 和 pom.xml文件
application.properties
- #dubbo 配置
- spring.dubbo.application.name=course-dubbo-service
- spring.dubbo.registry.address=zookeeper://${zookeeper.address}:2181
- spring.dubbo.protocol.name=dubbo
- spring.dubbo.protocol.port=20880
- #spring.dubbo.protocol.host=127.0.0.1
- spring.dubbo.scan=com.idig8.course
-
- #数据源的配置
- spring.datasource.url=jdbc:mysql://${mysql.address}:3306/db_course
- spring.datasource.username=root
- spring.datasource.password=root
- spring.datasource.driver-class-name=com.mysql.jdbc.Driver
-
- thrift.user.ip=user-thrift-service
- thrift.user.port=7911
pom.xml
- <?xml version="1.0" encoding="UTF-8"?>
- <project xmlns="http://maven.apache.org/POM/4.0.0"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
- <parent>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-parent</artifactId>
- <version>1.5.3.RELEASE</version>
- </parent>
- <modelVersion>4.0.0</modelVersion>
-
- <groupId>com.idig8</groupId>
- <artifactId>course-dubbo-service</artifactId>
- <version>1.0-SNAPSHOT</version>
-
- <dependencies>
- <dependency>
- <groupId>io.dubbo.springboot</groupId>
- <artifactId>spring-boot-starter-dubbo</artifactId>
- <version>1.0.0</version>
- </dependency>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter</artifactId>
- </dependency>
- <dependency>
- <groupId>org.apache.thrift</groupId>
- <artifactId>libthrift</artifactId>
- <version>0.10.0</version>
- </dependency>
-
- <dependency>
- <groupId>org.mybatis.spring.boot</groupId>
- <artifactId>mybatis-spring-boot-starter</artifactId>
- <version>1.3.1</version>
- </dependency>
-
- <dependency>
- <groupId>mysql</groupId>
- <artifactId>mysql-connector-java</artifactId>
- <version>5.1.44</version>
- </dependency>
- <dependency>
- <groupId>com.idig8</groupId>
- <artifactId>course-dubbo-service-api</artifactId>
- <version>1.0-SNAPSHOT</version>
- </dependency>
-
- <dependency>
- <groupId>com.idig8</groupId>
- <artifactId>user-thrift-service</artifactId>
- <version>1.0-SNAPSHOT</version>
- </dependency>
- <dependency>
- <groupId>com.idig8</groupId>
- <artifactId>user-thrift-service-api</artifactId>
- <version>1.0-SNAPSHOT</version>
- </dependency>
- </dependencies>
-
- <build>
- <plugins>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-compiler-plugin</artifactId>
- <version>2.3.2</version>
- <configuration>
- <source>1.8</source>
- <target>1.8</target>
- </configuration>
- </plugin>
- <plugin>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-maven-plugin</artifactId>
- <executions>
- <execution>
- <goals>
- <goal>repackage</goal>
- </goals>
- </execution>
- </executions>
- </plugin>
- </plugins>
- </build>
- </project>
Dockerfile编写
- FROM java:openjdk-8
- MAINTAINER liming www.idig8.com
-
- COPY target/course-dubbo-service-1.0-SNAPSHOT.jar /course-dubbo-service.jar
-
- ENTRYPOINT ["java","-jar","/course-dubbo-service.jar"]
build.sh 编写
- #!/usr/bin/env bash
-
- mvn package
- docker build -t course-dubbo-service:latest .
sh build.sh
创建容器
docker run -it course-dubbo-service:latest --redis.address=192.168.1.140 --zookeeper.address=192.168.1.140
跟之间的基本是一样的,都是重复的工作。直接看源码吧
docker run -it course-edge-service:latest --zookeeper.address=192.168.1.140
docker run -it gataway-zuul:latest
PS:把所有单独的服务,做成了镜像,下次想个办法服务和镜像统一的运行起来。
4-3 docker下的服务通讯(上)
4-4 docker下的服务通讯(下)
上次我们把每个服务都进行了docker化,并且确保了每个docker容器都可以运行起来,但是并没有让他们之间进行彼此的通信,这次就完成通信这件事。让docker服务运行起来,并且保证他们之间的正常通信。源码:https://github.com/limingios/msA-docker
以下的三种方案,如果使用redis,zookeeper,mysql那种是最优方案呢?这里的话我们开发的微服务使用第三种方案来做。针对redis,zookeeper,mysql使用第二种方案来做。
我们也可以通过命令一个一个之间进行link,但是compose得方式是一种常见的使用方式,也是最好的方案,它可以描述出来服务之间的关系,非常的清晰明了。
- version: '3'
-
- services:
- message-thrift-python-service:
- image: message-thrift-python-service:latest
-
- user-thrift-service:
- image: user-thrift-service:latest
- command:
- - "--mysql.address=192.168.1.130"
-
- user-edge-service:
- image: user-edge-service:latest
- links:
- - user-thrift-service
- - message-thrift-python-service
- command:
- - "--redis.address=192.168.1.130"
-
- course-dubbo-service:
- image: course-dubbo-service:latest
- links:
- - user-thrift-service
- command:
- - "--mysql.address=192.168.1.130"
- - "--zookeeper.address=192.168.1.130"
-
- course-edge-service:
- image: course-edge-service:latest
- links:
- - user-edge-service
- command:
- - "--zookeeper.address=192.168.1.130"
-
-
- gataway-zuul:
- image: gataway-zuul:latest
- links:
- - user-edge-service
- - course-edge-service
- ports:
- - 8080:8080
配置这个了大概2天才配置好,太麻烦了,真的一个人如果做微服务建议放弃,太费劲。但是微服务的好处真的很明显。
PS:三步走,服务docker化已经基本完成了,下一步创建docker仓库。
4-5 镜像仓库
三步走的第二步,开始查看镜像仓库,就一起学一学docker仓库,仓库分2种,别人家的仓库也叫公共仓库,自己的仓库也叫私有仓库。公共仓库和私有仓库最大的却别就是网速,公共仓库从公网,私有仓库是从局域网,速度的差别,安全性,公共的肯定没有私有的安全,保存在自己的硬盘上是最稳的。其实在中级的时候我已经说过docker仓库的创建,这次用mac本来实现docker仓库的创建。源码:https://github.com/limingios/msA-docker
- docker tag zookeeper:3.5 zhugeaming/zookeeper:3.5
- docker login
- docker push zhugeaming/zookeeper:3.5
docker pull registry:2
官方的安装说明
docker run -d -p 5000:5000 --name registry registry:2
- docker tag zookeeper:3.5 localhost:5000/zookeeper:3.5
- docker push localhost:5000/zookeeper:3.5
在生产环境下,并不能满足要求,单点登录,一个服务器出了问题,另一个服务器就很难托管过来,它并没有界面,给他交互不是很方便。
所以业内又出现了一个新的叫harbor,更适合生产环境中。
GitHub - goharbor/harbor: An open source trusted cloud native registry project that stores, signs, and scans content.
详细往下看 最终我放弃了mac下直接安装harbor,通过vagrant的方式虚拟机来安装harbor
选择线下安装
- cp Downloads/harbor-offline-installer-v1.6.1.tgz ~/app/
- cd ~/app
- tar -xvf harbor-offline-installer-v1.6.1.tgz
修改红色部分
- cd harbor
- ll
- vi harbor.cfg
方便管理,mac系统管理,修改存储路径
- vi docker-compose.yml
-
想在mac上安装一个harbor 下载了harbor-offline-installer-v1.6.1.tgz
https://github.com/limingios/msA-docker/tree/master/vagrant/harbor
vi harbor.cfg
vi docker-compose.yml
./install.sh
ifconfig
http://172.28.128.3:8888
用户名:admin
密码:Harbor12345
library 公开的所有用户都可以push
添加项目micro-service,私有项目
点击项目可以进入项目中。
生产环境,很多的机房,每个机房之间的网速是很快的,但是跨机房的,可能网速的稳定性和速度就差一些,每个机房部署一个harbor,在通过一个中心的harbor,当有镜像的自动去同步其他的服务器,复制规则是可以针对项目的,每个项目自己的一个复制规则。
分配开发人员
Harbor其实操作很简单,随便点点都了解了。
现在想想办法把镜像都推送到mico-service里面
sudo vi /etc/hosts
hub.idig88.com 已经配置了基础
docker tag java:openjdk-8 hub.idig88.com:8888/micro-service/java:openjdk-8
- The push refers to repository [hub.idig88.com:8888/micro-service/java]
- Get https://hub.idig88.com:8888/v2/: http: server gave HTTP response to HTTPS client
- vi /usr/lib/systemd/system/docker.service
- service docker restart
配置地址:本机mac。修改后点击app& Restart
- docker login http://hub.idig88.com:8888 -u liming -p 密码
- docker push hub.idig88.com:8888/micro-service/java:openjdk-8
- docker tag python:3.6 hub.idig88.com:8888/micro-service/python:3.6
- docker push hub.idig88.com:8888/micro-service/python:3.6
已经将基础的镜像推送到了镜像仓库中,现在需要修改对应的dockerfile文件,更改基础镜像的名称。修改配置文件
8个镜像全部到位
PS:经历了2天大概做了不下20小时,我最终还是放弃了mac下安装harbor的方式,时刻要记住mac只是个编辑器,不要什么都在上边装,很多时候通过虚拟机更类似生成环境。这一次说完了docker仓库,下次开始服务编排工具。感觉好爽啊!
4-6 三大平台扬帆起航
之前的博客跟着我进度的老铁,已经通过java和python写好了微服务,引入了docker,build了镜像,而且还有了自己的镜像仓库。下面可以着手部署了。
docker是不类似传统的服务,它需要一款服务编排的框架。
最早出现的,早在2013年就发布了第一个版本
google晚于Mesos
2016年才被大家所熟知
Mesos/kubernetes/Docker Swarm 最早将Mesos用于生产环境的创业公司。
他也是基于Mesos去做的,后来陆续支持了Swarm和k8s。
Mesos/kubernetes/SpringCloud,创始人是从美国微软回来的,一直打造自己的paas产品,和上边的异曲同工,开始支持 Mesos 后来也陆续支持k8s和springCloud的微服务框架。
PS: 国内这种公司还是很多的,他们致力于帮助互联网企业来使用docker。让企业不管是传统服务,还是微服务,都可以享受到docker带来的遍历。他们的方案基本都是安排基础服务框架做二次开发,并在实施过程中,增加生产环境必备的一些功能,一般都会有一个漂亮的UI,日志,监控,报警,配置管理等功能,他们面对的客户:没有技术能力生产级别的paas平台,还有对上云有迫切的需求,虽然有技术,但是没有资源的公司。
造成了一种三足鼎立的局面,各自有各自的特点,很长时间k8s,趋于稳定,出现问题可以查阅的资料也是越来越多,并且k8s的设计也是面向微服务,面向服务编排的,而且背后的老大是大名鼎鼎的google,这一切让他在2017的容器编排大战中脱颖而出,同时也比的Mesos和Swarm也开始支持k8s,但是对于学习者来说,他们的架构和思想都是值得我们来学习的。并且学习这个东西本身就是触类旁通,学会一个,对学其他有深入的了解。从设计和架构的角度去了解他们。从0开始手动部署,搭建他们的集群环境,最后咱们把之前开发的微服务把他运行起来,让他们去编排去调度。
PS:下次就开始实践吧!老铁,兴奋不!一起学习,坚持永远是最重要的!
第5章 服务编排-Mesos
5-1 了解Mesos
Mesos是Apache下的开源分布式资源管理框架,它被称为是分布式系统的内核。Mesos最初是由加州大学伯克利分校的AMPLab开发的,后在Twitter得到广泛使用。
http://mesos.apache.org/
在你的数据中心 运行数据(很多台数据的集合),就像运行在单个的资源池一样
Mesos 抽象出来CPU,内存,磁盘和其他计算机资源从物理机或者虚拟机中,使具有容错的和可伸缩的系统更容易的构建和简单的运行。如果是没有基础的老铁,可能是认为是直接把服务器的硬件插拔出来重新组建一台新的机器,其实不是这样的,他是通过软件的方式把需要的硬件设备抽取出来,统一的调度,合理的利用这些资源。
当twitter才开始的时候网站经历了爆炸式的增长,网站很不稳定。经常被流量击垮,每次网站挂的时候都会出现下面这张图片,看的多了大家都比较顺眼了,导致这张图也火了,“失败之鲸”! 为了解决这个失败之鲸的教育,twitter通过google的Borg系统中得到启发,然后就开发一个类似的资源管理系统来帮助他们摆脱可怕的“失败之鲸”!后来他们注意到加州大学伯克利分校AMPLab正在开发的名为Mesos的项目,这个项目的负责人是Ben Hindman,Ben是加州大学伯克利分校的博士研究生。后来Ben Hindman加入了Twitter,负责开发和部署Mesos。现在Mesos管理着Twitter超过30,0000台服务器上的应用部署。最后twitter把Mesos开源给apache。
保持一个月更新一个版本的频率,知道今天也依然保持这这个频率目前最新版本:1.7.0,由此也可以看到对市场的信心!
Mesos是如何让twitter摆脱失败之鲸呢,查看下图
看下图
这时候slave1 还剩余1cpu,1gb没有被占用,还可以继续分配给其他的任务来运行,其实调度器就是给Mesos谈判资源的,看看你有多少资源,需要运行一个程序看看资源够不够,如果够的话,我会告诉你我要在那台机器上进行运行,然后把执行器告诉master,master把执行器告诉slave,在slave上执行,执行器其实可以理解为一段代码,可以给master和slave对接的代码。为了实现一个slave中运行多个任务,Mesos使用了隔离模块,这模块使用了进程隔离的机制来运行这些任务。Mesos早在09年就开始使用了linux的隔离技术,后来Mesos增加了对docker的支持,就可以使用docker本身的隔离机制,单不管使用什么隔离机制都需要执行器全部的打包,并且发送给响应的slave,slave上启动。
mesos 并不能单独的存在,必要要有Framework配合存在,也知道mesos有各种各样的Framework负责运行各种各样的程序,Marathon适合长期运行的项目(数据库,应用服务等等),下面这个图就是mesos+Marathon和linux内核的对比。
Mesos的核心,保证集群内的所有用户平等的使用资源,这里的资源包括内存和CPU,磁盘等等。
Mesos只负责的资源的调度管理,各种程序都使用Mesos里面的资源,也可以自己来开发Framework。
门槛低是相对其他的服务编排工具,环境比较容易搭建按照文档基本不会遇见大问题,如果使用长期运行的服务可以使用Marathon这种服务就可以了。Marathon的环境搭建比较容易上手很快就搭建完毕了。如果你有特殊场景需要自己开发Framework
,恭喜你老铁你中奖了,门槛太高!
twitter创始人30万以上的服务在使用,apple的集群,youtube也使用了。国内的爱奇艺,数人科技也都使用的。
支持集群
给机器打标签,CPU高,内存高,硬盘好的,然后资源要约的时候给指定标签的机器。
相当于服务的注册中心。
执行调度器的时候,有针对机器的健康检查的功能,包括三种方式:http,tcp,shell命令的,比如:web服务要加入基于http的健康检查,访问固定的页面,如果访问的是200的话,服务是没问题的。如果访问连续多少次发现不健康也就是不是200的情况,停止重新启动一个服务。
自己启动一个服务,注册事件订阅,它就会自动的推送订阅的事件信息,包括服务停止,被杀掉等等吧。
比较好看的UI页面,api接口提供给调用者查看服务的状态。每个服务运行的实例,每个实例的状态,可以通过脚本集成API。
PS:这就是对Mesos和marathon 大概的理解。
5-2 画出Mesos集群架构图
上次我们了解了Mesos的原理,这次我们想办法给环境搭建起来,但是搭建环境之前,首选得有服务器,这边就拿mac本和虚拟机来搭建。4台服务器。所以感觉需要画一个架构图,明确下每台服务器上需要安装什么软件,方便之后的环境搭建,和了解他们之前是如何交互的。
PS:最基础的Mesos架构图我们就画好了,下次开始服务环境的构建。
5-3 集群环境搭建_A
5-4 集群环境搭建_B
5-5 集群环境搭建_C
5-6 调整微服务适应Mesos
5-7 微服务部署_A
5-8 微服务部署_B
5-9 微服务部署_C
第6章 服务编排-DockerSwarm
6-1 了解Swarm
6-2 集群环境搭建(上)
6-3 集群环境搭建(下)
6-4 调整微服务及服务配置
6-5 微服务部署
第7章 服务编排-Kubernetes
7-1 了解kubernetes(上)
7-2 了解kubernetes(下)
7-3 环境搭建前奏
7-4 预先准备环境
7-5 基础集群部署(上)
7-6 基础集群部署(下)
7-7 小试牛刀
7-8 kube-proxy和kube-dns
7-9 理解认证、授权
7-10 为集群添加认证授权(上)
7-11 为集群添加认证授权(下)
7-12 再试牛刀
7-13 部署我们的微服务
第8章 CICD和DevOps
8-1 了解CICD和DevOps
8-2 准备GitLab和Jenkins
8-3 CICD实践(上)
8-4 CICD实践(下)
第9章 课程总结
9-1 -课程总结
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。