《Docker+Kubernetes微服务容器化实践》笔记1

1-1 微服务

1-2 软件架构的进化

什么是软件架构?软件架构是在软件的内部,经过综合各种因素的考量、权衡,选择特定的技术,将系统划分成不同的部分并使这些部分相互分工,彼此协作,为用户提供需要的价值。

什么是单体架构,定义:功能、业务集中在一个发布包里,部署运行在同一进程中。

单体架构的优势:

易于开发、易于部署、易于测试、易于水平伸缩

单体架构面临的挑战:

代码膨胀,难以维护;构建、部署成本大;新人上手困难;创新困难;可扩展性差。

1-3 什么是微服务?

使用一套小服务来开发单个应用的方式,每个服务运行在独立的进程里,一般采用轻量级的通讯机制互联,并且它们可以通过自动化的方式部署。

微服务的特征:单一职责(登录注册);轻量级通信(平台/语言无关);隔离性;有自己的数据;技术多样性。

1-4 微服务架构图

假定业务场景:一个在线教育的网站的部分功能;用户可以登录注册,获取用户信息;有发送邮件或短信的功能;可以查看课程列表和对课程的基本CRUD。

1-5 微服务架构的优势和不足

优势:独立性、敏捷性、技术栈灵活、高效团队。

不足:额外的工作(服务的拆分)、数据的一致性、沟通成本。

跟我学过初级和中级的小伙伴,感谢有你们的支持哈哈,我开始高级篇了。这次设计到springboot 和spring cloud,重点是落地,之前的CICD涉及的面有老铁说最后镜像部署没说,我说高级一定补上的!

微服务

微服务最近几年比较火,不了解也没关系,可以理解成一个项目的模块吧,微服务运行在docker容器里面,如果管理docker容器用服务编排框架k8s。

为什么要继续高级篇

镜像生产环境的自动化部署

  • 不同行业IT系统更新频率

纵向的是系统的更新频率,黄色的是半年以上更新一次,绿色的部分是3-6个月,蓝色的部分是每个月都要更新。横向的是行业的细分:制造业,金融行业,互联网行业,交通物流行业,零售业。其中互联网的更新频次最高92%的服务每个月都要更新。其中应该有很多服务一个月要更新多次。我相信如果一个行业如果要有竞争的优秀,更新频率一定是在不断的提高的。这就会倒逼越来越多的企业加入转型,docker微服务就是方向。

『高级篇』docker容器来说微服务导学(一)

  • IT系统支撑所存在的问题

看2个比重最大的问题,系统复杂性越来越高,IT运维管理复杂,构建一个全功能团队困难。应用频繁的升级开发团队会非常的痛苦:企业业务系统经过多年的发展,系统往往非常庞大,复杂度非常的高,要改动其中任何一个小功能都需要部署整个应用,敏捷开发和快速的服务根本无从谈起,传统行业在传统的IT建设过程中往往会使用不同的技术,这就存在了技术之前的诧异很大,管理和运维就比较复杂,随着这些问题的凸显,企业向微服务进行转型需求越来越强烈。

『高级篇』docker容器来说微服务导学(一)

  • 微服务架构落地情况

6%的企业应用了spring cloud开发框架,9%采用了dobbo和其他的微服务框架,51%考虑云原生的架构方向转型(公有云,私有云),因此可以看出来绝对部分企业有转型的需求的。

『高级篇』docker容器来说微服务导学(一)

  • 2018年和2017年docker的使用情况对比图

docker从2017年的7% 升级了4个百分点达到11%。考虑使用docker的应用的越来越多,特别是100台 服务器 以上的。

『高级篇』docker容器来说微服务导学(一)

  • 2017年的docker情况

企业的关注度才不断的升高。docker的使用在不断的普及,容器的成熟,对微服务的落地提供了很多的基础,轻量化的容器是微服务的最佳环境。微服务在容器的环境下,在加上服务编排框架持续集成变成可能。

『高级篇』docker容器来说微服务导学(一)

  • 为何存在

腾讯,阿里,京东,包括新浪 都在使用docker。通过图片的数据和案例说明:docker,服务编排,微服务值得我们去学习。可能在不久的将来将会是每一位开发和运维的老铁不得不了解的技术。

『高级篇』docker容器来说微服务导学(一)

『高级篇』docker容器来说微服务导学(一)

『高级篇』docker容器来说微服务导学(一)

『高级篇』docker容器来说微服务导学(一)

高级具体说些什么?

  • 微服务
  1. 传统架构演变微服务
  2. 微服务架构的架构图优势的不足
  • 微服务带来的问题和解决方案

从实战的角度出发

1.传统服务和微服务对比的方式来进行学习。

  1. 问题和业界的解决方案(springboot + spring cloud)
  2. 很多老铁都是搞 java 的,了解下springboot 和 cloud跟微服务,跟docker的关系,跟服务编排框架的关系。
  • 微服务实例开发

通过业务场景。从0开始一行不拉的开发完整个项目,微服务的开发有一个深刻的体会,服务开发过程中我们会用到,dubbo,spring boot ,spring cloud,网关,thrift。

体会到:dubbo的远程调用,thrift跨语言的接口调用,spring boot快速开发。

  1. 几个微服务,微服务需要怎么来划分。
  2. 他们之前的关系,他们是如何划分的。

『高级篇』docker容器来说微服务导学(一)

  • 部署前的准备
  1. 服务docker化,调整配置,制作成docker镜像。
  2. docker-compose,运行在docker容器中,保证容器间的正常通信。
  3. docker仓库,harbor搭建,push镜像。
  4. kubernetes,mesos,swarm,初级我讲了mesos,中级主要讲了swarm,高级重点还是k8s。其实他们都是学会其中一种,基本都是庞统的。
  • 服务编排
  1. 了解mesos,画出架构图,集群环境,部署微服务
  2. 了解swarm,集群环境,调整服务,部署服务
  3. 压轴k8s,通过2017年docker承认k8s后,服务编排的领导地位,2017年爆发增长,企业上docker首选k8s,门槛太高了,光服务搭建都能压死人。了解概念,基础集群搭建,小试牛刀,服务发现,认证授权,部署微服务。
  • CICD和Devops

jekens 和gitlab 中级欠大家jekens这次补给大家。目的是从代码提交到流程更新全部自动化。

环境参数

  1. 做java的常用的IDEA
  2. 消息服务用 python 3.6.3
  3. RPC框架Thrift0.10.0 跨语言
  4. Ubuntu-16.04
  5. Docker-18.03
  6. kubernetes-1.9.0

技术储备

  • 熟悉java 后台开发
  • 熟悉docker基本命令,镜像容器什么的
  • 熟悉 linux 基本操作

PS:整体把握微服务,清晰理解微服务的各种概念,如果开发微服务,技术栈之间的微服务通信,怎么样把一个服务运行在docker容器里,服务之间是如何建立连接的,多种编排框架下服务的编排和服务的发现扩容。docker绝对是你以后必经只选。来我们一起努力,成为更好的自己。

也工作了10年了,对于软件的架构也是不断学习总结,怎么样的发展到微服务的架构。

什么是软件架构

在软件的内部,经过综合各种因素的考量,权衡选择特定的技术,将系统划分不同的部分并使这些相互分工,彼此写作,为用户提供需要的价值。

  • 哪些因素
  1. 业务需求
  2. 技术栈
  3. 成本
  4. 组织架构
  5. 可扩展性
  6. 可维护性
  • 以我的个人经历
  1. 一层架构

    2007年在河南本地的一个公司实习,负责的是一个老系统,它用到了jsp和servlet,jdbc的技术,java早期的标准技术,在jsp里面看到了html,还看到了一大片一大片的 java 代码,直接写在jsp里面。在servlet里面有上千行的代码,300,500行都很平常的事情,包含了业务逻辑,返回给jsp的业务内容,业务操作,数据库操作。维护起来让你很崩溃,不过才毕业也就忍了,坚持了半年。后来要去济南。这种在极其简单的业务里面还是可行的,但是现在也看不到了。

  2. MVC

    2008年去了济南,济南毕竟要全国知名的公司就进入了。虽然是996,但是感觉还好,至少代码不那么复杂了,虽然是jsp,java代码基本没有,分了很多文件夹,层次清晰分工明确,也学到MVC的三层架构。解决了代码调用杂乱无章,让代码清晰,通过各层之间定义接口的方式,让接口和实现分离,可以将原来的实现替换成方案,让别人理解,降低了沟通成本,维护成本,分工的明确各司其职,很长时间都是软件的架构经典模式。像SSH 和SSM其实MVC的实现。

  3. dubbo

    2013年换了一家公司,dubbo那时候才出来1年,公司尝试用dubbo改造一个核心系统,为什么要用dubbo,因为里面java代码加页面代码100多万行,需求每个月还不断的添加,牛逼了我的哥!3年以上的人至少2-3个月熟悉都不一定能上手,只能想办法拆分,拆分的过程也是对老代码进行梳理和重构,dubbo的出现可以让前后端物理上隔离开来,完全变成2个可以单独维护的模块,从感官上复杂度就下降了一半,这种开发历程,在河南这边可能不太明显,在北上广应该都有类似的经历。多年的开发的人员。

其实上边的说的都是单体架构,很多目前的公司也都是单体架构,虽然dubbo,分离成了前后2个个体,但他并不是微服务。

什么是单体架构

功能,业务集中在一个发布包里,部署运行在同一个进程中。

  • 优势
  1. 易于开发(方便开发人员开发)
  2. 易于测试(准备一台服务器,部署下就可以测试了)
  3. 易于部署(所有代码都打在一个包里面,直接拷贝一个war部署在 服务器 上,目录中)
  4. 易于水平伸缩(节点的复制,新建服务器,配置好运行环境,直接拷贝一个war部署在服务器上)
  • 单体面临的挑战

    >随着很多传统行业往互联网考虑,业务变化瞬息万变,系统的升级也越来越频繁,用户的数量快速增长,单体架构已经无法满足互联网的发展了,它有很多致命的硬伤。

  1. 代码膨胀,难以维护(出现bug,分析定位成本都很高,随着代码开发,开发人员对全局的理解越来越缺失,修复一个bug,可能引入其他bug,恶性循环,导致难以维护)
  2. 构建,部署成本大(代码越来越多,构建部署启动的时间越来越长,项目维护的人越来越多,大家都在构建,都在部署,难免互相影响,难免造成一个bug的修复,提交给测试验证的时间拉的很长,效率越来越底下)
    3.新人上手困难(现在的互联网公司,都是铁打的营盘流水的兵,过于复杂新人还没完全理解上手的时候,就已经离职了)
  3. 创新困难(成功引入新框架困难,就算成功引入学习成本极高)
  4. 可扩展性差(代码都运行在同一个进程里面,一个进程只能运行在一天机器上,给这个机器加多少内存,加多少cpu才能够我们这个项目用呢,有的框架对CPU要求高,有的框架对内存要求高,有的框架对硬盘要求高,其实最终选择了一个各方面都好的机器,是不是增加了成本的开支)

PS:综上所述,单体架构已经out了,老铁,可以考虑其他了,如何考虑下回继续说。

上一节说了单体架构,单体架构也无法适应我们的服务,来说说微服务,看能否解决单体架构的问题。

什么是微服务

最近两,三年才出现的新名词,虽然时间还不是很长,几乎每个软件从业人员对它有影响,也都通过微服务,很多人都意识到微服务对软件行业的影响。

  • 定义

    > 使用一套小服务来开发单个应用的方式,每个服务运行在独立的进程中,一般采用轻量级的通讯机制互联,并且他们可以通过自动化的方式部署。

  • 多微才算微
  1. 代码量?

    可是我们语言不同,不同的语言写相同的业务可能代码量差距非常大。

    人来判断,实习期的开发人员和有5年以上开发经验的人员写的代码量也是有差距的。

  2. 开发时间?

    影响开发速度的因素太多太多,个人的经验,擅长开发的语言,对业务的理解。

  3. 不可度量

    实际是一种设计思路,设计思想,而不是固定的一个量

  • 微服务的特征
  1. 单一职责

    订单和支付,登录和注册,跟其他业务不太紧密的可以单独做成一个服务邮件,短信服务。

  2. 轻量级通信

    轻量级的通信协议,简单来说平台无关语言无关。http。

  3. 隔离性

    每个微服务在自己的内存中,相互之间不会干扰。

  4. 有自己的数据

    业务数据的独立性。每个都有自己的业务数据库,降低业务的复杂度

5.技术多样性

开发人员选择最适合的开发语言,提供出应有的api。

  • 微服务诞生背景
  1. 互联网行业的快速发展

    技术变化快,用户数量变化快

  2. 敏捷开发。精益方法深入人心

    用最小的代价,做最快的迭代,得到最有用的反馈。频繁的修改测试上线。

  3. 容器技术的成熟

    容器技术没有成熟之前,微服务很难落地的,docker的出现解决了犹豫微服务数量的旁边运维的瓶颈。使微服务的落地成为可能。

PS:docker让微服务成为可能,感谢容器化技术的成熟!

第2章 微服务带来的问题及解决方案分析

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

很多人把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
  1. dubbo
  2. motan
  3. dubbox
  4. grpc
  5. thrift
  • MQ

消息队列,实际场景用的不太多,例如之前说的滴滴打车这种就是消息订阅的模式。

如何选择RPC框架

RPC是微服务方面最多的一种情况,也是选择比较多的情况,可选的RPC框架也非常的多,选择一个RPC框架是需要面临的问题。

  • I/O,线程调度模型

长连接,短连接,单线程,多线程,线程调度算法的性能

  • 序列化的方式

可读的(XML,JSON),二进制(FASTJSON),为什么要考虑序列化呢,因为序列的效率直接影响到我们通信的效率,扩大了序列化和反序列化的时间,RPC的效率,同一个对象如果序列化小的话大大提升效率。

  • 多语言支持

根据团队语言,如果是多语言就需要找支持多语言的RPC框架,如果单语言例如都是java,就直接dubbo只支持java。

  • 服务治理

比如有没有服务发现,服务监控,一个拥有服务治理的RPC框架,一般支持集群的部署和服务高可用。

目前流程RPC框架有哪些

  • Dubbo/DubboX

2014年10月份,dubbo就不在维护了,时隔3年dubbo又重新开始维护,一来用户量确实很多,二来微服务比较火,对微服务更好的支持。DubboX是在阿里的dubbo基础上开发的一套DubboX。只支持java语言。

  • Motan

一套新浪微博的,2016年5月进行的开源,号称每天支持新浪微博的千亿级别的调用量,通过spring的调用方式不需要额外的代码就具有分布式的能力。只支持java语言。

  • Thrift

2007年facebook开发的,08年进入了apche项目,它是一个跨语言的。毕竟那么多年,你想到的它都支持。没有服务治理相关的东西。

  • GRPC

google开源的一个项目,跟Thrift相似,也支持跨语言。

对比

PS:微服务通信的根本就是RPC通信,比http效率高,稳定性好。

2-3 服务发现、部署更新和扩容

服务发现

所有的表现形式都是ip+端口的形式。

  • 传统服务

服务比较少的话,可以通过下面的方式。如果服务很多的话,基本运维人员都崩溃死了。

  • 微服务

服务太多的话,需要一种服务发现的机制。

  1. 客户端的发现
  2. 服务端的发现

部署更新和扩容

  • 传统服务

适合小项目,服务少,服务器少。

  1. 新服务的部署,代码写好,内网测试通过,上线,跟运维交涉那台服务器比较空闲,资源比较吃紧的情况,需要等待服务器到来在进行部署。服务器有了告诉运维将那个应用拷贝到我们的服务器上,可能通过ftp或者是自动化的方式,如果是web方法会需要拷贝一个tomcat分配一个端口号。查询服务器那些端口被用了,找一个没有被占用的,跟你的域名做域名解析,修改nginx,反向代理指向刚才的tomcat上。
  2. 更新直接ftp或者自动化更新下旧代码,直接用新代码更新就可以了。
  3. 任何应用都是2个实例,让服务高可用,所以更新代码的时候需要更新2次。下线一台,更新代码,上线。如果自动化做的不太好的话,基本人工成本很高
  4. 扩容跟部署一样都比较麻烦。
  • 微服务

服务数据居多,更新上线频繁。微服务如何解决这些问题呢
什么是服务排版,服务的发现,服务部署,服务更新,扩容,简化。

流行的服务编排工具

可以解决微服务遇到本节问题的解决

  • Mesos
  • docker swarm
  • k8s

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的项目,有点麻烦配置文件太多了。

  • 独立运行 java -jar *.jar

原来的需要一个web服务器,tomcat,代码发布到服务器的指定位置。

  • 内嵌web服务器

讲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。

  • 一系列框架

集合框架

  • 简化java的分布式系统

spring boot 简化了java的开发,spring cloud简化了分布式系统的开发(分布式系统的基础设施的开发)

  • springboot的封装

几家服务框架进行了组合,通过springboot的风格进行封装,基于springboot的一款开发工具。

boot 和cloud

  • springboot意在简化,是一种开发,配置风格
  • springcloud意在简化分布式,是功能的集合,风格的统一

cloud vs 微服务

  • java cloud的初衷简化微服务的开发和部署
  • java微服务的解决方案
  • 侧重的是功能和开发

提供多台机器,部署了spring cloud的应用,但是他们之间的运维spring cloud做不了的。

  • 其实最终springcloud开发出来的应用最终是docker image,方便重启应用需要使用服务编排工具。

spring cloud的核心组件

  • netfilx eureka

服务发现组件

  • netfilx ribbon

客户端负载均衡组件

  • netfilx Hystrix

调度器

  • netfilx Zuul

服务网关

  • Spring cloud config

分布式配置

发现没有里面很多都是netfilx,netfilx 其实是个美国在线影视公司。说说他的历史吧,我学什么语言喜欢看看他的历史。很久很久以前,有一家公司叫Blockbuster,称霸租碟业许多年。某个叫Reed Hastings的哥们在那里租了个碟,结果由于超期归还被黑走“一大笔”逾期费(大概40美元),怒了。然后他忿忿地去健身,发觉健身房商业模式甚是美哉,不管你去得多还是少,会员费半毛钱也不能少交。很不巧,Hastings是一个动不动就要改变世界的软件工程师,想法来了就要干,更不巧的是他当时已经非常有钱。于是愤怒之余他创办了Netflix,也是做租碟生意,没有逾期费并且搞会员制。十三年后Netflix把Blockbuster干到了破产保护,大仇得报。这个故事告诉我们两个道理:
1.客户服务一定要做好,不该薅的羊毛就别死命薅,不然你就是逼羊为虎。
2.工程师惹不起。

  • netfilx 开源了很多分布式的解决方案,但是直接使用目标麻烦,门槛比较高,spring cloud把他们拿过来做了跟spring的整合,让他跟spring的整合更高,

介绍下spring cloud的组件

主要了解原理,不会深入介绍spring cloud。

  • netfilx eureka

  • netfilx ribbon

  • netfilx hystrix

  • netflix zuul

  • spring cloud config

PS:下面我们一步一步spring cloud+spring boot创建的微服务,部署在服务编排框架上。

第3章 微服务开发

3-1 微服务业务分析

从本节开始微服务的开发,说到开发有几个问题需要解决,首先要知道我们需要开发什么?什么样的业务场景,分析业务场景,有几个微服务,每个微服务需要完成什么样的功能,微服务之间的关系,之间的依赖关系,他们之间是如何通迅的,这些都了解的之后,我们就可以进入开发阶段了。

业务场景

  • 用户可以注册和登录

现在的登录系统一般都是单点登录,支持跨域,在去使用其他系统的时候就不需要登录了,最好是不要使用session,最好是无状态的,避免使用session。

  • 登录用户可以对课程进行CURD操作

上边这个不是大而全的系统,只是微服务的功能,老铁咱们的目的很明确是搞微服务,不是学web开发的,我们通过上边的几个功能上从0开始了解微服务,一行不拉的完成开发微服务,让大家去开发有个真切的体会的。

基本的微服务的流程

  1. 用户访问api网关
  2. api网关访问2个用户的edgeservice(java) 和 课程的edgeservice(java)
  3. 这2个接口提供的http协议(rest)
  4. 用户的edgeservice 访问的用户服务
  5. 课程的edgeservice 访问的课程服务
  6. 用户服务对外接口是Thrift 跨语言的协议
  7. 课程服务对外接口使用Dubbo,只限java
  8. 用户服务(java) 后端有个数据库
  9. 课程服务(java) 后端也有一个数据库
  10. 信息服务(python)对外接口是Thrift (用户注册的时候,可能发送短信,或者邮件确认可能需要信息服务)
  11. redis 来进行用户的信息存储,提供给用户服务使用
  12. 课程的edgeservice 和 课程服务 通过 zookeeper 来完成服务的发现
  13. 用户查询课程 需要调用课程服务,这也就是微服务之间的调用

PS:接下来,老铁跟我一起完成微服务的建设和搭建过程。

3-2 Thirft安装和验证

从这节开始微服务的开发阶段,首选根据下面的图,选择一个模块开始微服务的开发,我的开发习惯的就是检一些对比人依赖少的进行开发,找到了『信息服务』对其他依赖最少的,就开始开发这个,之前也说过thrift主要说的都是理论,这次咱们直接实战,先通过安装使用开始。源码:https://github.com/limingios/msA-docker

3-3 Python开发信息服务 

Thrift 安装

  • 下载安装

  • 下载地址

  • windows下的安装

Thrift 使用

namespace 编译的语言 包名
其实thrift的语言跟java语法很类似

  • 新建文件

  • 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

  • py的语法

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开发信息服务

信息服务准备用python来写,在现有的idea中添加python的模块。源码:https://github.com/limingios/msA-docker

idea安装python插件

安装后重新idea。

安装python模块

安装thrift的pyhon插件

开始我用idea写python,下载个插件都费劲,我换成了pycharm来写美滋滋

  • 编辑Python的服务代码
# 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")

  • 查看端口已经启动

  • 生成对应java 和python的命令

都是根据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的接口,依赖于下面的信息服务,后端有数据库,开发一个服务,首选需要设计对外的接口,都给别人提供什么样的服务。

用户服务接口创建

  • pom编辑

<?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>
  • 创建对应这个接口提供服务的thrift

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);
}

  • 命令生成java对应的接口类

thrift --gen java -out ../src/main/java user-service.thrift

没有建立环境变量,直接在目录下生成的,然后拷贝到对应的目录下。

  • 生成2个类,一个是实体类,一个对应的user-service( 这都是通过thrift自动生成的)

因自动生成源码太多,直接看文章头的连接地址下载吧

  • 实现user-thrift-serive-api的接口

  • 推荐大家使用docker的方式安装mysql,之前我的中级有在windows、mac环境下安装docker的方式

# 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地址来远程访问,新建数据库表

  • user-thrift-service 使用springboot的方式

这些都是springboot的基本操作,建议看我的源码吧。

PS:老铁可能感觉很乱,我把思路从头到尾说一下

  1. 编写thrift的文件
  2. 建立一个api接口,也就是user-thrift-service-api,通过thrift生成对应的java类
  3. 建立user-thrift-service,通过pom引用user-thrift-service-api的jar包文件。
  4. user-thrift-service 里面建立service,实现里面user-thrift-service-api的jar包接口方法。
  5. 通过引入mybatise 实现数据库调用dao,service引入dao,完成接口
  6. ThriftServer 引入的接口方法,启动服务代码,实现RPC开通properties里面的端口配置

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

新建maven模块user-edge-service

  • 引入user-thrift-service-api 和 message-thrift-service-api的pom文件

<?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>

  • redis的工具类,用于无状态的存储,token信息 保存用的userInfo
    RedisConfig

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 和 LoginResponse 统一返回字符串

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;
    }

}


  • controller 引入thrift的service方法和redis的操作工具类 用于redis的操作

因为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;
    }
}

测试准备工作

  1. 数据库内添加一条记录(如果不添加记录会报getUsername是null,通过在线网站生成MD5的密码加密)
  2. 启动user-thrift-service 和 message-thrift-service 2个服务
  3. 启动user-edge-service
  4. 工具调用接口访问user-edge-service里面的接口,查看是否正常返回调用服务是否成功
  5. 登录操作查看是否redis内有存储内容

流程梳理

  1. 建立2个thrift api项目 user 和 message 通过thrift文件生成对应语言的方法。
  2. 建立2个服务端项目,各自引入user和message的api项目。
  3. 多种语言比较特殊,例如message里面需要两边都通过python端需要通过thirft生成对应的python代码方便python制作server端。java端调用需要通过
    thirft升成对应的java代码方便其他项目的引用。
  4. 如果都是单语言的话,都是java的话,只需要生成一个thrift的java代码,和对应的server端服务端代码就可以了。
  5. 对于使用端只需要引用2个api就可以实现RPC的调用。但是需要记住的是他们之前的协议和传输内容必须一致才可以完成通信。

user-edge-service-client 新建--单点登录的服务

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

dubbo 介绍

高性能的基于java的,RPC框架。dubbo是阿里巴巴开源的一个项目,就像大多的RPC框架,dubbo的思想围绕一个服务,指定一个方法的参数和返回值,这个方法可以被远程调用的,在服务端,会实现这个接口,会运行dubbo的服务用来处理客户端的调用,在客户端,会有一个存根它提供和服务端想通的方法。其实这些概念用java的术语:首先要定义一个接口,这个接口在服务端和客户端公用,服务端会完成这个接口的实现,客户端通过接口的描述来调用服务端。

快速入门讲解 spring 版本,

  • 根据官网来创建

创建三个api,provider,consumer 三个项目

  • 用了本地调用。
    provider.xml

<?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>

快速入门讲解springboot版本

SpringBoot整合dubbo示例

一、定义API(API模块)

1. 定义api

package com.idig8.springboot.dubbo.demo;

public interface DemoService {
    String sayHello(String name);
}

2. install到本地

对外提供的maven坐标如下:

<dependency>
    <groupId>com.idig8</groupId>
    <artifactId>dubbo-demo-api</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>

二、服务提供者(provider模块)

1. 增加maven依赖

<!-- 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>

2. 实现API接口

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)";
    }

}

3. springboot配置文件 - application.properties

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

4. 启动类

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);
    }
}

三、服务消费者(consumer模块)

1. 增加maven依赖

<!-- 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>

2. 实现controller

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);
    }

}

3. springboot配置文件 - application.properties

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

4. 启动类

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

66e8c2032151357ad077934f3530a640.png

开发课程服务模块

基于dubbo的服务,一般先定义api接口,前面的都是基于thrift的,我们先写一个thrift的文件,然后根据配置文件升成对应的api,dubbo我们相当于先手写一个api的模块。

稍微复杂

  • 总体思想

就不在复制代码了只通过截图来讲述功能,可以参考源码,本次跟用到了上次springboot集成dubbo的方式。

  1. course-dubbo-service-api 是负责提供接口的服务
  2. course-dubbo-service 是course-dubbo-service-api的实现
  3. course-dubbo-service 需要依赖原始的user-thrift-service-api的原生DTO类 和 user-thrift-service中的方法实现。
  4. course-dubbo-service调用user-thrift-service 是用过thrift的方式完成的调用。因为user-thrift-service本身就是通过thrift生成对应的java类。

  • user-thrift-service 和 user-thrift-service-api
  1. user-thrift-service-api 做了thrift的修改,增加了老师的DTO和ID来获取用户的信息,所以需要重新通过thrift命令生成对应的java类
  2. user-thrift-service 实现了新增的通过ID来获取用户的信息的接口,并增加了UserMapper
  3. user数据库中增加了一张关联表teacher表

  • course-dubbo-service-api 和 course-dubbo-service
  1. course-dubbo-service pom中引入了springboot,mysql的驱动,thrift,springboot-dubbo,user-thrift-service.jar,user-thrift-service-api.jar
  2. course-dubbo-service 中注入thrift微服务的访问service,获取教师针对课程的教程信息访问user-thrift-service的微服务,通过thrift的方式。
  3. application.properties 配置dubbo的配置,数据库配置,user-thrift-service的地址和端口
<?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

新建模块course-edge-servce

  • pom增加依賴
<?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>

『高级篇』docker之开发课程EdgeService(16)

原创文章,欢迎转载。转载请注明:转载自IT人故事会,谢谢!
原文链接地址:『高级篇』docker之开发课程EdgeService(16)

课程的edgeService依赖于课程服务的dubbo服务,对外提供的restAPI,跟用户的EdgeService有点类似,只是一个调用的是thrift,一个调用的是dubbo,比较特殊的是课程的EdgeService需要用户登录后才可以访问,如果没有登录的话,需要跳转到登录系统才可以访问。源码:https://github.com/limingios/msA-docker

新建模块course-edge-servce

  • pom增加依賴
<?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>

  • controller 调用course-dubbo-service-api 中的接口
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();
    }
}

  • 增加filter组件
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;
    }
}

  • application.properties
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

业务流程梳理

  1. 课程EdgeService 依赖用户EdgeService服务,Thrift用户服务,课程服务。
  2. 课程 EdgeService pom 依赖了用户登录user-edge-service-client,user-edge-service-client用于检测用户是否登录功能。需要调用用户的服务。
  3. 当用户完成登录后,课程EdgeService 访问课程服务,获取课程的列表信息。
  4. 课程EdgeService 依赖用户EdgeService服务,Thrift用户服务登录控制,登录后的跳转功能。
  5. 课程EdgeService 依赖与course-dubbo-service-api服务,用于获取课程信息和用户的课程信息。

梳理下dubbo的思路

  1. 建立对应的api项目定义方法。最终提供一个jar包供调用方和服务提供方使用。
  2. 服务实现方引用api项目,实现里面的功能,提供端口,名称,地址,zookeeper监控中心。
  3. 服务调用方引用api项目,引用zookeeper的监控中心发现服务。直接调用服务就可以用服务实现方的方法了。

程序演示

  • 启动服务(按照顺序)
  1. user-thrift-service
  2. user-edge-service
  3. course-dubbo-service
  4. course-edge-service
  • 界面演示
  1. 访问http://127.0.0.1:8081 自动跳转到http://127.0.0.1:8082/user/login
  2. 登录获取到token
    3.访问地址http://127.0.0.1:8081/course/courseList?token=ux4g5z98mowv0qr6r6e6ietdo00nh0vl

PS:微服务跟之前说的一样就是互相通过RPC的方式进行通信,之间有自己的数据库,只是RPC暴露接口的方式来获取其他的微服务之间的数据。

3-14 APIGatewayZuul 

这次说最后一个模块APIGateway,他的功能就是将我们客户端的请求统一的转发到用户和课程的EdgeService上面,ApiGetway我们使用springClud来实现。源码:https://github.com/limingios/msA-docker

使用springClud做路由转发功能

  • 新建项目

<?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。

微服务部署

  1. 服务docker化,可以在docker下运行。
  2. Docker仓库创建,docker创建的镜像push到仓库里面。
  3. 构建高可用的集群环境,Mesos,Swarm,kubernetes。运行这3个服务编排工具把我们的服务运行和编排起来。可以优雅的启动,停止,扩充容,故障的恢复。

docker化

服务有个适合的环境,服务可以运行起来,给他准备一个环境,比如服务是个种子,我们需要准备一片土地,服务是一条鱼,就需要准备一片大海。源码中的服务有一个是python写的,有一个是java写的,也就是需要2个运行环境,一个基于java,一个基于python。

  • java镜像

去hub.docker.com 搜索java,找到tag

  • 下载java镜像

先配置加速,道客镜像站

 docker pull java:openjdk-8
 docker images|grep jdk

  • 安装jdk容器
docker run -it --entrypoint bash java:openjdk-8
java -version

开始dockerfile的开发

熟悉docker的老铁应该都知道,如果要编译docker的话需要docker化,首选需要开发dockerfile文件。

  • 准备工作

文件中不能有写死的情况,如果写死了,每次服务的变更都需要变更镜像。为了减少构建镜像的过程,尽量吧数据库的访问地址,经常会发生变化的东西,需要踢出去,不要在配置文件中配置死,针对数据库的访问,不能地址直接写死,mysql的地址,当服务运行在docker之后,他的ip是实时都在变化的,不能写死在镜像里,直接就找不到了就报错了。还有个问题,我们的服务以什么样的形式放在我们docker里面,springboot之所以说适用于微服务,他有个很大的好处,它可以将咱们的服务构建成一个fat jar,只有一个jar包,然后通过java的一个命令:java -jar 文件.jar 运行起来,这种方式对于微服务来说也是很友好的,也非常的简单,就使用这种方式来做。变量的方式就可以通过springboot --mysql.address 就可以传递进来了。

  • 构建user-thrift-service

修改配置文件 和 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

新建立build.sh

#!/usr/bin/env bash

mvn package
docker build -t user-thrift-service:latest .

  • 构建message-thrift-python-service

后面很多的服务,都依赖它,必须把它做好。它是一个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

  • 构建user-edge-service

微服务的依赖,了解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
  • 构建course-dubbo-service

微服务的依赖,了解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

  • 构建course-edge-service

跟之间的基本是一样的,都是重复的工作。直接看源码吧

  1. 修改pom文件增加maven构建
  2. 增加dockerfile文件
  3. 增加build.sh文件
  4. 资源文件的修改

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

说到让docker彼此进行通信。

以下的三种方案,如果使用redis,zookeeper,mysql那种是最优方案呢?这里的话我们开发的微服务使用第三种方案来做。针对redis,zookeeper,mysql使用第二种方案来做。

  1. 直接通信,容器的IP和端口直接访问服务。这种方式运行的很少,因为docker的ip不稳定,每次重新都可能发生变化。
  2. 服务把端口映射出去,将服务的端口映射到主机的端口上,依赖它的容器去访问主机的ip和端口就可以了。
  3. 通过link的方式,link到主机后,直接通过主机的名字来进行访问。

使用docker compose的方式

我们也可以通过命令一个一个之间进行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天才配置好,太麻烦了,真的一个人如果做微服务建议放弃,太费劲。但是微服务的好处真的很明显。

  1. 修改完每个微服务,都要进行build.sh
  2. 服务之间的调用,通过dockerlink的方式在docker-compose都进行了配置。
  3. 查看微服务的日志通过docker logs 容器名称
  4. docker ps 查看微服务的id

PS:三步走,服务docker化已经基本完成了,下一步创建docker仓库。

4-5 镜像仓库 

三步走的第二步,开始查看镜像仓库,就一起学一学docker仓库,仓库分2种,别人家的仓库也叫公共仓库,自己的仓库也叫私有仓库。公共仓库和私有仓库最大的却别就是网速,公共仓库从公网,私有仓库是从局域网,速度的差别,安全性,公共的肯定没有私有的安全,保存在自己的硬盘上是最稳的。其实在中级的时候我已经说过docker仓库的创建,这次用mac本来实现docker仓库的创建。源码:https://github.com/limingios/msA-docker

公有仓库

最权威的公有仓库就是hub.docker.com

  • 打包
docker tag zookeeper:3.5 zhugeaming/zookeeper:3.5
docker login
docker push zhugeaming/zookeeper:3.5

私有仓库(一)

Docker

docker pull registry:2

  • 安装说明

官方的安装说明

docker run -d -p 5000:5000  --name registry registry:2

  • push 到本地的registry
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

  1. 将文件放在了mac本的/user/liming/app/目录下,并解压
  2. 修改了配置文件和挂载文件的路径。
  3. 运行install的时候必须加sudo,负责权限不足,加了sudu后可以正常安装并安装成功。
  4. 但是剩余的问题来了,里面的容器无法访问挂载的配置文件,提示权限不足。为了弥补权限不足的问题,我在docker-compose.yml做了配置都增加了
    privileged: true,但是不生效,困扰了几天身边没有docker方面的人,帮忙解答下。

vagrant 方式安装

https://github.com/limingios/msA-docker/tree/master/vagrant/harbor

  • 修改host文件和端口,可以不修改端口
vi harbor.cfg

vi docker-compose.yml

  • 安装
 ./install.sh 

  • 浏览
ifconfig

  • 访问harbor

http://172.28.128.3:8888
用户名:admin
密码:Harbor12345

library 公开的所有用户都可以push

添加项目micro-service,私有项目

点击项目可以进入项目中。

  • 添加成员
  1. 项目管理员 等于 admin
  2. 开发人员 等于 对项目的镜像 有push 和pull的权限
  3. 访客 等于 只有 pull的权限

  • 复制功能

生产环境,很多的机房,每个机房之间的网速是很快的,但是跨机房的,可能网速的稳定性和速度就差一些,每个机房部署一个harbor,在通过一个中心的harbor,当有镜像的自动去同步其他的服务器,复制规则是可以针对项目的,每个项目自己的一个复制规则。

  • 创建用户

  • 添加成员

分配开发人员

Harbor其实操作很简单,随便点点都了解了。

本地的微服务镜像推送

现在想想办法把镜像都推送到mico-service里面

  • host文件修改
sudo vi /etc/hosts 
  • 1

  • 上传基础镜像

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文件,更改基础镜像的名称。修改配置文件

  • 6个微服务Dockerfile

From hub.idig88.com:8888/micro-service/

  • 6个sh脚本修改增加了推送功能

查看仓库

8个镜像全部到位

PS:经历了2天大概做了不下20小时,我最终还是放弃了mac下安装harbor的方式,时刻要记住mac只是个编辑器,不要什么都在上边装,很多时候通过虚拟机更类似生成环境。这一次说完了docker仓库,下次开始服务编排工具。感觉好爽啊!

4-6 三大平台扬帆起航 

之前的博客跟着我进度的老铁,已经通过java和python写好了微服务,引入了docker,build了镜像,而且还有了自己的镜像仓库。下面可以着手部署了。

服务编排

docker是不类似传统的服务,它需要一款服务编排的框架。

  • Mesos

最早出现的,早在2013年就发布了第一个版本

  • kubernetes

google晚于Mesos

  • Docker Swarm

2016年才被大家所熟知

在这三款产品之上,也有很多公司在研发自己的产品

  • 数人云

Mesos/kubernetes/Docker Swarm 最早将Mesos用于生产环境的创业公司。
他也是基于Mesos去做的,后来陆续支持了Swarm和k8s。

  • 灵雀云

Mesos/kubernetes/SpringCloud,创始人是从美国微软回来的,一直打造自己的paas产品,和上边的异曲同工,开始支持 Mesos 后来也陆续支持k8s和springCloud的微服务框架。

PS: 国内这种公司还是很多的,他们致力于帮助互联网企业来使用docker。让企业不管是传统服务,还是微服务,都可以享受到docker带来的遍历。他们的方案基本都是安排基础服务框架做二次开发,并在实施过程中,增加生产环境必备的一些功能,一般都会有一个漂亮的UI,日志,监控,报警,配置管理等功能,他们面对的客户:没有技术能力生产级别的paas平台,还有对上云有迫切的需求,虽然有技术,但是没有资源的公司。

  • 最近这几年这3个平台都所有发展

造成了一种三足鼎立的局面,各自有各自的特点,很长时间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,由此也可以看到对市场的信心!

  1. 2013年1月份发布了0.12.1版本
  2. 中间经历了30多个版本
  3. 2016年7月份发布了1.0.0
  • 两级调度

Mesos是如何让twitter摆脱失败之鲸呢,查看下图

  1. Mesos master是支持高可用集群的,通过zookeeper完成的主节点选举
  2. Mesos master管理所有节点的Mesos slave的守护进程
  3. Mesos slave运行在物理机或者虚拟机之上。每个运行具体的任务或者是服务
  4. 每个salve在启动的时候都会注册是master
  5. master协调每个节点的slave,并且确定每个slave的可用资源
  6. 第一级调度 Hadoop 和Mpi ,master 调度slave
  7. 第二级调度 Framework的组件组成,大家看下下图的虚线部分,Framework包括调度器和执行器两部分,Master可以跟多种Framework进行通信,上边展示的调度器也就Hadoop 和 MPI,还可以用多种。下边展示的是执行器,执行器运行在slave中。

  • 调度的流程

看下图

  1. slave1也就是Agent1告诉master空闲资源有4cpu,4gb
  2. Mesos触发调度资源分配模块,请求分配4cpu,4gb,Framework1要求使用可用资源,然后master发出要约描述了slave1中所有的可用资源。4cpu,4gb内存
  3. Famework的调度器给master说需要在slave上运行2个task,task1(2cpu,1gb)
    task2(1cpu,2gb)
  4. Mesos向slave下发任务,并且分配适当的资源,给Franmework的执行器,接下来有执行器启动这2个任务,fw1,task1; fw2,task2;

这时候slave1 还剩余1cpu,1gb没有被占用,还可以继续分配给其他的任务来运行,其实调度器就是给Mesos谈判资源的,看看你有多少资源,需要运行一个程序看看资源够不够,如果够的话,我会告诉你我要在那台机器上进行运行,然后把执行器告诉master,master把执行器告诉slave,在slave上执行,执行器其实可以理解为一段代码,可以给master和slave对接的代码。为了实现一个slave中运行多个任务,Mesos使用了隔离模块,这模块使用了进程隔离的机制来运行这些任务。Mesos早在09年就开始使用了linux的隔离技术,后来Mesos增加了对docker的支持,就可以使用docker本身的隔离机制,单不管使用什么隔离机制都需要执行器全部的打包,并且发送给响应的slave,slave上启动。

Marathon

mesos 并不能单独的存在,必要要有Framework配合存在,也知道mesos有各种各样的Framework负责运行各种各样的程序,Marathon适合长期运行的项目(数据库,应用服务等等),下面这个图就是mesos+Marathon和linux内核的对比。

整合mesos marathon 负载均衡 服务发现的流程

Mesos特征

  • 强大的资源管理

Mesos的核心,保证集群内的所有用户平等的使用资源,这里的资源包括内存和CPU,磁盘等等。

  • Kernel 和Framework的分离

Mesos只负责的资源的调度管理,各种程序都使用Mesos里面的资源,也可以自己来开发Framework。

  • 门槛较低,易于使用

门槛低是相对其他的服务编排工具,环境比较容易搭建按照文档基本不会遇见大问题,如果使用长期运行的服务可以使用Marathon这种服务就可以了。Marathon的环境搭建比较容易上手很快就搭建完毕了。如果你有特殊场景需要自己开发Framework
,恭喜你老铁你中奖了,门槛太高!

  • 大厂使用

twitter创始人30万以上的服务在使用,apple的集群,youtube也使用了。国内的爱奇艺,数人科技也都使用的。

Marathon特征

  • 高可用

支持集群

  • Constraints

给机器打标签,CPU高,内存高,硬盘好的,然后资源要约的时候给指定标签的机器。

  • 服务发现和负载均衡

相当于服务的注册中心。

  • 健康检查

执行调度器的时候,有针对机器的健康检查的功能,包括三种方式:http,tcp,shell命令的,比如:web服务要加入基于http的健康检查,访问固定的页面,如果访问的是200的话,服务是没问题的。如果访问连续多少次发现不健康也就是不是200的情况,停止重新启动一个服务。

  • 事件订阅

自己启动一个服务,注册事件订阅,它就会自动的推送订阅的事件信息,包括服务停止,被杀掉等等吧。

  • 完善的REST API

比较好看的UI页面,api接口提供给调用者查看服务的状态。每个服务运行的实例,每个实例的状态,可以通过脚本集成API。

PS:这就是对Mesos和marathon 大概的理解。

5-2 画出Mesos集群架构图 

上次我们了解了Mesos的原理,这次我们想办法给环境搭建起来,但是搭建环境之前,首选得有服务器,这边就拿mac本和虚拟机来搭建。4台服务器。所以感觉需要画一个架构图,明确下每台服务器上需要安装什么软件,方便之后的环境搭建,和了解他们之前是如何交互的。

  1. 有四台服务器,一台是主机(HOST),其他三台都是虚拟机(Server01,Server02,Server03)
  2. 有一个组件是 Mesos Master放在Server02这台机器上。
  3. Mesos Master 是通过zookeeper实现高可用的,通过zookeeper来进行选组,Marathon也是通过zookeeper来找到对应的主节点通信,本人的mac本的内存只有8g,所以就一个MesosMaster,如果机器比较牛逼的老铁,可以三个虚拟机都创建3个Mesos Master,其中任何一个Mesos Master挂掉的话,其他不受影响依然继续高可用。
  4. zookeeper 运行在 Host上边。
  5. Server01 和 Server03 上边运行 Mesos Slave
  6. Marathon这个Framework框架运行在Server02上,主要充当服务的调度和服务的发现,老铁也可以运行多个Marathon在不同机器上实现高可用,它跟Marathon进行通信实现服务的调度。
  7. Mesos Master 会管理 Mesos slave,给自己的salve派发任务。
  8. Mesos Master 和 Mesos slave 都会注册到zookeeper上。
  9. Marathon-lb也运行Host主机上。Marathon-lb 会跟Marathon进行通信。订阅Marathon的组件,知道Marathon管理的Mesos下的所有Slave的状态,以及对外的ip和端口号。
  10. 最后在主机上运行一个浏览器,浏览器通过Marathon lb去访问到我们所有的服务。

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 -课程总结 

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值