微服务架构
之前找到一个英汉词典库是用python写的,将其中读取词典的功能用能用go语言实现了一下,可以从词典库中读取词条。然后想着将这些词条批量的插入到postgresql数据库中,可以做成一个web应用,在插入的过程中遇到插入效率的问题,20万条词条依次插入数据库是相当耗费时间的,可以使用事物大数据指令等办法提高插入速度。如果不把这20万的数据看做词条而是不同用户输入数据,那么按单条插入是比较理想的模拟方式。现在准备将这个插入过程进行修改,采用微服务的构架,看是否能提供速度。
采用微服务的方式,就需要将整个功能进行分解,整个词典入口功能会产生平静的地方是数据读取,以及数据入库。在单一应用的过程中,词条是按顺序读取,然后利用go语言的并发机制压入数据库,这样就对数据库产生极大的压力,相当于一下子有几百上千的连接,会频繁的看到数据库无相应或超时。而改成一次一条又慢的厉害。所以现在准备在数据库前增加消息队列,读取的数据条先到消息队列,然后建立多个消息的消费者,这些消费者在将这些词条分别放入数据库,这样数据库的压力就分解到消息队列和多个消费者上。
这样整个功能需要包含以下服务:
- 词条采集 读取词典中的词条,然后发送到消息队列中
- 消息队列
- 词条入库服务 将消息队列传送过来的词条数据插入数据库
- postgresql 关系型数据库
采用微服务方式,就不太容易从控台查看状态,需要增加一个日志收集服务,将词条入库的过程进行汇总,也方便了解整个功能的效率。另外还需要增加配置中心,服务和数据库的信息都配置到配置中心,这样就需要一个服务发现的过程。然后还需要将整个系统简化,提供一个单独工具,用来创建数据表将消息队列等配置信息注册到配置中心(现在不知道怎么能将postgresql直接注册到配置中心)。
前面是整个功能的组成部分和要实现的功能。其中的服务发现,数据入库,消息队列等都是通用功能,准备建立一个公共函数库,这样除了第三方的服务还需要以下几个部分:
- 公共函数库 提供日志记录,数据库操作,消息队列操作,服务注册
- 初始化工具 初始化数据库,将数据库消息队列的信息注册到配置中心
- 词条读取 从词典中读取词条并将词条发送给消息队列
- 词条入库 将消息队列传递过来的词条写入数据库
为了统一管理和配置,将多数服务都配置在配置中心,按服务名称去访问配置中心的信息。另外也将消息队列的主题名称也配置在配置中心,服务启动之后将自己也注册到配置中心。
进行的是英文词典,消息队列的主题名称en-dict,频道为en。需要手动注册的服务有:
- postgresql 目标数据库
- elasticsearch 日志记录
- nsq 消息队列
功能组成
公共函数库 base
包含公共操作的函数,诸如日志,数据库,消息队列等的操作和调用。
日志
日志准备使用zerolog,然后通过hooks的方式将日志信息传递给elasticsearch,主要是没有找到zerolog是否可以直接将日志信息传递给elasticsearch。
需要将整个日志操作封装成对象,在服务启动的时候去执行初始化操作,然后返回的是zerolog的对象,使用这个对象去进行日志添加。在初始化的时候如果没有设置elasticsearch,那么就不需要将日志添加到elasticsearch中。
先将消息发送到消息队列,之前测试elasticsearch的时候一直没办法插入数据。
数据库
封装一个postgresql的操作对象,通过数据库连接参数去建立连接,使用默认的数据库操作对象去执行数据库操作。
CREATE TABLE IF NOT EXISTS public.dict_en(
id serial PRIMARY KEY,
word text NOT NULL,
pronunciation text[] NOT NULL,
paraphrase text[] NOT NULL,
rank text,
pattern text,
sentence jsonb,
createtime timestamp without time zone NOT NULL DEFAULT LOCALTIMESTAMP,
updatetime timestamp without time zone NOT NULL DEFAULT now()
)
消息队列
消息队列准备采用nsq,分成两个部分,一个是消息的消费,一个是消息的生成
配置中心
配置中心准备采用etcd,在这里按服务名称去添加查找服务的配置信息,在服务名称前增加统一的前缀/dict
。
初始化工具 init
这个部分是独立的工具,负责向配置中心写入目标数据库,日志服务,消息队列地址。
将数据写入etcd的服务中,需要写入以下服务:
- postgresql
- nsq
这样准备先将信息写到yml的配置文件中,然后使用etcd接口读取。
按服务名称读取服务列表中的服务,然后将信息以json串形式写入etcd中。
之前使用的google.golang.org/grpc v1.27.1 // indirect
,结果一直报错,这个项目是新创建的,之前在其他库中一直都是正常的,经过对比正常的库是google.golang.org/grpc v1.25.1 // indirect
,在go.mod中将这个库替换为低版本就可以正常运行。
go run ./main.go
# github.com/coreos/etcd/clientv3/balancer/picker
../../go/pkg/mod/github.com/coreos/etcd@v3.3.18+incompatible/clientv3/balancer/picker/err.go:37:44: undefined: balancer.PickOptions
../../go/pkg/mod/github.com/coreos/etcd@v3.3.18+incompatible/clientv3/balancer/picker/roundrobin_balanced.go:55:54: undefined: balancer.PickOptions
# github.com/coreos/etcd/clientv3/balancer/resolver/endpoint
../../go/pkg/mod/github.com/coreos/etcd@v3.3.18+incompatible/clientv3/balancer/resolver/endpoint/endpoint.go:114:78: undefined: resolver.BuildOption
../../go/pkg/mod/github.com/coreos/etcd@v3.3.18+incompatible/clientv3/balancer/resolver/endpoint/endpoint.go:182:31: undefined: resolver.ResolveNowOption
词条读取 reader
词条入库服务 writer
启动后先从etcd中读取数据库,消息队列信息,如果没有读取到etcd,重复执行3次检查,无效就结束当前应用。如果能读取到,分别去尝试连接消息队列和数据库,如果都不能连接也结束当前应用。正常之后应该将自己注册到etcd上,这样可以放便其他应用知道当前服务是否可用。
连接正常之后,检查是否创建数据表,如果没有创建先创建,然后一直监听目标主题,当有消息进来,执行入库操作。按nsq的数据流转方式,在一个频道下的消费者不会被发送相同的消息,这样只要词条读取不重复,这里应该也不会重复。
所有的数据都是通过etcd的配置中读取的,需要访问的服务名称都是固定,这样只需要传递etcd的服务地址。
ServiceConfig
终于算是构建了一个基本的etcd读取对象,可以读取更新和读取etcd中的数据,接下来可以继续完成整个微服务框架的构建了。