12306 的架构到底有多⽜逼? 绘你⼀世倾城 Java后端 2019-11-04 每到节假⽇期间,⼀⼆线城市返乡、外出游玩的⼈们⼏乎都⾯临着⼀个问题:抢⽕⻋ 票! 点击上⽅ Java后端,选择 设为星标 优质⽂章,及时送达 链接 | 绘你⼀世倾城 来源 | httpjuejin.im/post/5d84e21f6fb9a06ac8248149 1122330066 抢抢票票,,极极限限并并发发带带来来的的思思考考 虽然现在⼤多数情况下都能订到票,但是放票瞬间即⽆票的场景,相信⼤家都深有体会。 尤其是春节期间,⼤家不仅使⽤ 12306,还会考虑“智⾏”和其他的抢票软件,全国上下⼏亿⼈在这段时间都在抢票。 “12306 服务”承受着这个世界上任何秒杀系统都⽆法超越的 QPS,上百万的并发再正常不过了! 笔者专⻔研究了⼀下“12306”的服务端架构,学习到了其系统设计上很多亮点,在这⾥和⼤家分享⼀下并模拟⼀个例⼦:如 何在 100 万⼈同时抢 1 万张⽕⻋票时,系统提供正常、稳定的服务。 Github代码地址: https://github.ccoomm/GuoZhaoran/spikeSystem ⼤⼤型型⾼⾼并并发发系系统统架架构构 ⾼并发的系统架构都会采⽤分布式集群部署,服务上层有着层层负载均衡,并提供各种容灾⼿段(双⽕机房、节点容错、服务 器灾备等)保证系统的⾼可⽤,流量也会根据不同的负载能⼒和配置策略均衡到不同的服务器上。 下边是⼀个简单的⽰意图:
负负载载均均衡衡简简介介 上图中描述了⽤⼾请求到服务器经历了三层的负载均衡,下边分别简单介绍⼀下这三种负载均衡。 11… OOSSPPFF((开开放放式式最最短短链链路路优优先先)是是⼀⼀个个内内部部⽹⽹关关协协议议((IInntteerriioorr GGaatteewwaayy PPrroottooccooll,,简简称称 IIGGPP) OSPF 通过路由器之间通告⽹络接⼝的状态来建⽴链路状态数据库,⽣成最短路径树,OSPF 会⾃动计算路由接⼝上的 Cost 值,但也可以通过⼿⼯指定该接⼝的 Cost 值,⼿⼯指定的优先于⾃动计算的值。 OSPF 计算的 Cost,同样是和接⼝带宽成反⽐,带宽越⾼,Cost 值越⼩。到达⽬标相同 Cost 值的路径,可以执⾏负载均 衡,最多 6 条链路同时执⾏负载均衡。 22… LLVVSS((LLiinnuuxx VViirrttuuaall SSeerrvveerr) 它是⼀种集群(Cluster)技术,采⽤ IP 负载均衡技术和基于内容请求分发技术。 调度器具有很好的吞吐率,将请求均衡地转移到不同的服务器上执⾏,且调度器⾃动屏蔽掉服务器的故障,从⽽将⼀组服务 器构成⼀个⾼性能的、⾼可⽤的虚拟服务器。 33… NNggiinnxx 想必⼤家都很熟悉了,是⼀款⾮常⾼性能的 HTTP 代理/反向代理服务器,服务开发中也经常使⽤它来做负载均衡。这⾥有 ⼀份Nginx不错的⽂章:Nginx 从⼊⻔到实战 Nginx 实现负载均衡的⽅式主要有三种: 轮轮询询加加权权轮轮询询 IIPP HHaasshh 轮轮询询
下⾯我们就针对 Nginx 的加权轮询做专⻔的配置和测试。 NNggiinnxx 加加权权轮轮询询的的演演⽰⽰ Nginx 实现负载均衡通过 Upstream 模块实现,其中加权轮询的配置是可以给相关的服务加上⼀个权重值,配置的时候可 能根据服务器的性能、负载能⼒设置相应的负载。 下⾯是⼀个加权轮询负载的配置,我将在本地的监听 3001-3004 端⼝,分别配置 1,2,3,4 的权重: #配置负载均衡 upstream load_rule { server 127.0.0.1:3001 weight=1; server 127.0.0.1:3002 weight=2; server 127.0.0.1:3003 weight=3; server 127.0.0.1:3004 weight=4; }… server { listen 80; server_name load_balance.com www.load_balance.com; location / { proxy_pass http://load_rule; } }我在本地 /etc/hosts ⽬录下配置了 www.load_balance.com 的虚拟域名地址。 接下来使⽤ Go 语⾔开启四个 HTTP 端⼝监听服务,下⾯是监听在 3001 端⼝的 Go 程序,其他⼏个只需要修改端⼝即可: ppaacckkaaggee main iimmppoorrtt ( “net/http” “os” “strings” )ffuunnccmmaaiinn() { http.HandleFunc(“/buy/ticket”, handleReq) http.ListenAndServe(“:3001”, nil) }//处理请求函数,根据请求将响应结果信息写⼊⽇志 ffuunncchhaannddlleeRReeqq(w http.ResponseWriter, r *http.Request) { failedMsg := “handle in port:” writeLog(failedMsg, “./stat.log”) }//写⼊⽇志 ffuunncc wwrriitteeLLoogg(msg ssttrriinngg, logPathssttrriinngg) { fd, _ := os.OpenFile(logPath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644) ddeeffeerr fd.Close() content := strings.Join([]ssttrriinngg{msg, “\r\n”}, “3001”) buf := []bbyyttee(content) fd.Write(buf) }我将请求的端⼝⽇志信息写到了 ./stat.log ⽂件当中,然后使⽤ AB 压测⼯具做压测:
aabb -n 1000 -c 100 http://www.load_balance.ccoomm/buy/ticket 统计⽇志中的结果,3001-3004 端⼝分别得到了 100、200、300、400 的请求量。 这和我在 Nginx 中配置的权重占⽐很好的吻合在了⼀起,并且负载后的流量⾮常的均匀、随机。 具体的实现⼤家可以参考 Nginx 的 Upsteam 模块实现源码,这⾥推荐⼀篇⽂章《Nginx 中 Upstream 机制的负载均 衡》: https://www.kancloud.cn/digest/understandingnginx/202607 秒秒杀杀抢抢购购系系统统选选型型 回到我们最初提到的问题中来:⽕⻋票秒杀系统如何在⾼并发情况下提供正常、稳定的服务呢? 从上⾯的介绍我们知道⽤⼾秒杀流量通过层层的负载均衡,均匀到了不同的服务器上,即使如此,集群中的单机所承受的 QPS 也是⾮常⾼的。如何将单机性能优化到极致呢? 要要解解决决这这个个问问题题,,我我们们就就要要想想明明⽩⽩⼀⼀件件事事::通常订票系统要处理⽣成订单、减扣库存、⽤⼾⽀付这三个基本的阶段。 我们系统要做的事情是要保证⽕⻋票订单不超卖、不少卖,每张售卖的⻋票都必须⽀付才有效,还要保证系统承受极⾼的并 发。这三个阶段的先后顺序该怎么分配才更加合理呢?我们来分析⼀下: 下下单单减减库库存存 当⽤⼾并发请求到达服务端时,⾸先创建订单,然后扣除库存,等待⽤⼾⽀付。 这种顺序是我们⼀般⼈⾸先会想到的解决⽅案,这种情况下也能保证订单不会超卖,因为创建订单之后就会减库存,这是⼀ 个原⼦操作。 但是这样也会产⽣⼀些问题: 在极限并发情况下,任何⼀个内存操作的细节都⾄关影响性能,尤其像创建订单这种逻辑,⼀般都需要存储到磁盘数据 库的,对数据库的压⼒是可想⽽知的。 如果⽤⼾存在恶意下单的情况,只下单不⽀付这样库存就会变少,会少卖很多订单,虽然服务端可以限制 IP 和⽤⼾的
购买订单数量,这也不算是⼀个好⽅法。 ⽀⽀付付减减库库存存 如果等待⽤⼾⽀付了订单在减库存,第⼀感觉就是不会少卖。但是这是并发架构的⼤忌,因为在极限并发情况下,⽤⼾可能 会创建很多订单。 当库存减为零的时候很多⽤⼾发现抢到的订单⽀付不了了,这也就是所谓的“超卖”。也不能避免并发操作数据库磁盘 IO。 预预扣扣库库存存 从上边两种⽅案的考虑,我们可以得出结论:只要创建订单,就要频繁操作数据库 IO。 那么有没有⼀种不需要直接操作数据库 IO 的⽅案呢,这就是预扣库存。先扣除了库存,保证不超卖,然后异步⽣成⽤⼾订 单,这样响应给⽤⼾的速度就会快很多;那么怎么保证不少卖呢?⽤⼾拿到了订单,不⽀付怎么办? 我们都知道现在订单都有有效期,⽐如说⽤⼾五分钟内不⽀付,订单就失效了,订单⼀旦失效,就会加⼊新的库存,这也是现 在很多⽹上零售企业保证商品不少卖采⽤的⽅案。 订单的⽣成是异步的,⼀般都会放到 MQ、Kafka 这样的即时消费队列中处理,订单量⽐较少的情况下,⽣成订单⾮常快,⽤ ⼾⼏乎不⽤排队。 扣扣库库存存的的艺艺术术 从上⾯的分析可知,显然预扣库存的⽅案最合理。我们进⼀步分析扣库存的细节,这⾥还有很⼤的优化空间,库存存在哪⾥? 怎样保证⾼并发下,正确的扣库存,还能快速的响应⽤⼾请求?
在单机低并发情况下,我们实现扣库存通常是这样的: 为了保证扣库存和⽣成订单的原⼦性,需要采⽤事务处理,然后取库存判断、减库存,最后提交事务,整个流程有很多 IO,对 数据库的操作⼜是阻塞的。 这种⽅式根本不适合⾼并发的秒杀系统。接下来我们对单机扣库存的⽅案做优化:本地扣库存。 我们把⼀定的库存量分配到本地机器,直接在内存中减库存,然后按照之前的逻辑异步创建订单。 Tips:欢迎⼤家关注微信公众号:Java后端,来获取更多推送。 改进过之后的单机系统是这样的: 这样就避免了对数据库频繁的 IO 操作,只在内存中做运算,极⼤的提⾼了单机抗并发的能⼒。 但是百万的⽤⼾请求量单机是⽆论如何也抗不住的,虽然 Nginx 处理⽹络请求使⽤ Epoll 模型,c10k 的问题在业界早已 得到了解决。 但是 Linux 系统下,⼀切资源皆⽂件,⽹络请求也是这样,⼤量的⽂件描述符会使操作系统瞬间失去响应。 上⾯我们提到了 Nginx 的加权均衡策略,我们不妨假设将 100W 的⽤⼾请求量平均均衡到 100 台服务器上,这样单机所 承受的并发量就⼩了很多。 然后我们每台机器本地库存 100 张⽕⻋票,100 台服务器上的总库存还是 1 万,这样保证了库存订单不超卖,下⾯是我们 描述的集群架构:
问题接踵⽽⾄,在⾼并发情况下,现在我们还⽆法保证系统的⾼可⽤,假如这 100 台服务器上有两三台机器因为扛不住并发 的流量或者其他的原因宕机了。那么这些服务器上的订单就卖不出去了,这就造成了订单的少卖。 要解决这个问题,我们需要对总订单量做统⼀的管理,这就是接下来的容错⽅案。服务器不仅要在本地减库存,另外要远程 统⼀减库存。 有了远程统⼀减库存的操作,我们就可以根据机器负载情况,为每台机器分配⼀些多余的“Buffer 库存”⽤来防⽌机器中有 机器宕机的情况。 我们结合下⾯架构图具体分析⼀下: 我们采⽤ Redis 存储统⼀库存,因为 Redis 的性能⾮常⾼,号称单机 QPS 能抗 10W 的并发。 在本地减库存以后,如果本地有订单,我们再去请求 Redis 远程减库存,本地减库存和远程减库存都成功了,才返回给⽤⼾ 抢票成功的提⽰,这样也能有效的保证订单不会超卖。 当机器中有机器宕机时,因为每个机器上有预留的 Buffer 余票,所以宕机机器上的余票依然能够在其他机器上得到弥补, 保证了不少卖。 Buffer 余票设置多少合适呢,理论上 Buffer 设置的越多,系统容忍宕机的机器数量就越多,但是 Buffer 设置的太⼤也会
对 Redis 造成⼀定的影响。 虽然 Redis 内存数据库抗并发能⼒⾮常⾼,请求依然会⾛⼀次⽹络 IO,其实抢票过程中对 Redis 的请求次数是本地库存 和 Buffer 库存的总量。 因为当本地库存不⾜时,系统直接返回⽤⼾“已售罄”的信息提⽰,就不会再⾛统⼀扣库存的逻辑。 这在⼀定程度上也避免了巨⼤的⽹络请求量把 Redis 压跨,所以 Buffer 值设置多少,需要架构师对系统的负载能⼒做认真 的考量。 代代码码演演⽰⽰ Go 语⾔原⽣为并发设计,我采⽤ Go 语⾔给⼤家演⽰⼀下单机抢票的具体流程。 初初始始化化⼯⼯作作 Go 包中的 Init 函数先于 Main 函数执⾏,在这个阶段主要做⼀些准备性⼯作。 我们系统需要做的准备⼯作有:初始化本地库存、初始化远程 Redis 存储统⼀库存的 Hash 键值、初始化 Redis 连接池。 另外还需要初始化⼀个⼤⼩为 1 的 Int 类型 Chan,⽬的是实现分布式锁的功能。 也可以直接使⽤读写锁或者使⽤ Redis 等其他的⽅式避免资源竞争,但使⽤ Channel 更加⾼效,这就是 Go 语⾔的哲学: 不要通过共享内存来通信,⽽要通过通信来共享内存。 Redis 库使⽤的是 Redigo,下⾯是代码实现:
… //localSpike包结构体定义 ppaacckkaaggee localSpike ttyyppee LocalSpike ssttrruucctt { LocalInStock iinntt6644 LocalSalesVolume iinntt6644 }… //remoteSpike对hash结构的定义和redis连接池 ppaacckkaaggee remoteSpike //远程订单存储健值 ttyyppee RemoteSpikeKeys ssttrruucctt { SpikeOrderHashKey ssttrriinngg //redis中秒杀订单hash结构key TotalInventoryKey ssttrriinngg //hash结构中总订单库存key QuantityOfOrderKey ssttrriinngg //hash结构中已有订单数量key }//初始化redis连接池 ffuunncc NNeewwPPooooll() *rreeddiiss.PPooooll { rreettuurrnn &redis.Pool{ MaxIdle: 10000, MaxActive: 12000, // max number of connections Dial:ffuunncc() (redis.Conn, error) { c, err := redis.Dial(“tcp”, “:6379”) iiff err != nil { panic(err.Error()) }rreettuurrnn c, err }, } }… ffuunncc iinniitt() { localSpike = localSpike2.LocalSpike{ LocalInStock: 150, LocalSalesVolume: 0, }remoteSpike = remoteSpike2.RemoteSpikeKeys{ SpikeOrderHashKey: “ticket_hash_key”, TotalInventoryKey: “ticket_total_nums”, QuantityOfOrderKey: “ticket_sold_nums”, }redisPool = remoteSpike2.NewPool() done = make(cchhaann iinntt, 1) done <- 1 }本本地地扣扣库库存存和和统统⼀⼀扣扣库库 本地扣库存逻辑⾮常简单,⽤⼾请求过来,添加销量,然后对⽐销量是否⼤于本地库存,返回 Bool 值: ppaacckkaaggee localSpike //本地扣库存,返回bool值 ffuunncc (spike *LocalSpike) LLooccaallDDeedduuccttiioonnSSttoocckk() bbooooll{ spike.LocalSalesVolume = spike.LocalSalesVolume + 1 rreettuurrnn spike.LocalSalesVolume < spike.LocalInStock }注意这⾥对共享数据 LocalSalesVolume 的操作是要使⽤锁来实现的,但是因为本地扣库存和统⼀扣库存是⼀个原⼦性 操作,所以在最上层使⽤ Channel 来实现,这块后边会讲。
统⼀扣库存操作 Redis,因为 Redis 是单线程的,⽽我们要实现从中取数据,写数据并计算⼀些列步骤,我们要配合 Lua 脚 本打包命令,保证操作的原⼦性: package remoteSpike … const LuaScript = local ticket_key = KEYS[1] local ticket_total_key = ARGV[1] local ticket_sold_key = ARGV[2] local ticket_total_nums = tonumber(redis.call('HGET', ticket_key, ticket_total_key)) local ticket_sold_nums = tonumber(redis.call('HGET', ticket_key, ticket_sold_key)) -- 查看是否还有余票,增加订单数量,返回结果值 iiff(ticket_total_nums >= ticket_sold_nums) tthheenn return redis.call('HINCRBY', ticket_key, ticket_sold_key, 1) end return 0
//远端统⼀扣库存 func (RemoteSpikeKeys *RemoteSpikeKeys) RemoteDeductionStock(conn redis.Conn) bool { lua := redis.NewScript(1, LuaScript) result, err := redis.Int(lua.Do(conn, RemoteSpikeKeys.SpikeOrderHashKey, RemoteSpikeKeys.TotalInventoryKey, RemoteSpikeKeys.QuantityOfOrderKey)) iiff err != nil { return false }return result != 0 }我们使⽤ Hash 结构存储总库存和总销量的信息,⽤⼾请求过来时,判断总销量是否⼤于库存,然后返回相关的 Bool 值。 在启动服务之前,我们需要初始化 Redis 的初始库存信息: hmset ticket_hash_key “ticket_total_nums” 10000 “ticket_sold_nums” 0 响响应应⽤⽤⼾⼾信信息息 我们开启⼀个 HTTP 服务,监听在⼀个端⼝上: ppaacckkaaggee main … ffuunnccmmaaiinn() { http.HandleFunc(“/buy/ticket”, handleReq) http.ListenAndServe(“:3005”, nil) }上⾯我们做完了所有的初始化⼯作,接下来 handleReq 的逻辑⾮常清晰,判断是否抢票成功,返回给⽤⼾信息就可以了。
ppaacckkaaggee main //处理请求函数,根据请求将响应结果信息写⼊⽇志 ffuunncchhaannddlleeRReeqq(w http.ResponseWriter, r *http.Request) { redisConn := redisPool.Get() LogMsg := “” <-done //全局读写锁 iiff localSpike.LocalDeductionStock() && remoteSpike.RemoteDeductionStock(redisConn) { util.RespJson(w, 1, “抢票成功”, nil) LogMsg = LogMsg + “result:1,localSales:” + strconv.FormatInt(localSpike.LocalSalesVolume, 10) } eellssee { util.RespJson(w, -1, “已售罄”, nil) LogMsg = LogMsg + “result:0,localSales:” + strconv.FormatInt(localSpike.LocalSalesVolume, 10) }done <- 1 //将抢票状态写⼊到log中 writeLog(LogMsg, “./stat.log”) }ffuunncc wwrriitteeLLoogg(msg ssttrriinngg, logPathssttrriinngg) { fd, _ := os.OpenFile(logPath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644) ddeeffeerr fd.Close() content := strings.Join([]ssttrriinngg{msg, “\r\n”}, “”) buf := []bbyyttee(content) fd.Write(buf) }前边提到我们扣库存时要考虑竞态条件,我们这⾥是使⽤ Channel 避免并发的读写,保证了请求的⾼效顺序执⾏。我们将 接⼝的返回信息写⼊到了 ./stat.log ⽂件⽅便做压测统计。 单单机机服服务务压压测测 开启服务,我们使⽤ AB 压测⼯具进⾏测试: aabb -n 10000 -c 100 http://127.0.0.1:3005/buy/ticket 下⾯是我本地低配 Mac 的压测信息:
This iiss ApacheBench, Version 2.3 <$revision: 1826891=“”> Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ Licensed ttoo The Apache Software Foundation, http://www.apache.org/ Benchmarking 127.0.0.1 (bbee patient) Completed 1000 requests Completed 2000 requests Completed 3000 requests Completed 4000 requests Completed 5000 requests Completed 6000 requests Completed 7000 requests Completed 8000 requests Completed 9000 requests Completed 10000 requests Finished 10000 requests Server Software: Server Hostname: 127.0.0.1 Server Port: 3005 Document Path: /buy/ticket Document Length: 29 bytes Concurrency Level: 100 Time taken ffoorr tests: 2.339 seconds Complete requests: 10000 Failed requests: 0 Total transferred: 1370000 bytes HTML transferred: 290000 bytes Requests per second:4275.96 [#/sec] (mean) Time per request: 23.387 [ms] (mean) Time per request: 0.234 [ms] (mean, across aallll concurrent requests) Transfer rate: 572.08 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 0 8 14.7 6 223 Processing: 2 15 17.6 11 232 Waiting: 1 11 13.5 8 225 Total: 7 23 22.8 18 239 Percentage of the requests served withina certain time (ms) 50% 18 66% 24 75% 26 80% 28 90% 33 95% 39 98% 45 99% 54 100% 239 (longest request) 根据指标显⽰,我单机每秒就能处理 4000+ 的请求,正常服务器都是多核配置,处理 1W+ 的请求根本没有问题。 ⽽且查看⽇志发现整个服务过程中,请求都很正常,流量均匀,Redis 也很正常:
//stat.log … result:1,localSales:145 result:1,localSales:146 result:1,localSales:147 result:1,localSales:148 result:1,localSales:149 result:1,localSales:150 result:0,localSales:151 result:0,localSales:152 result:0,localSales:153 result:0,localSales:154 result:0,localSales:156 … 总总结结回回顾顾 总体来说,秒杀系统是⾮常复杂的。我们这⾥只是简单介绍模拟了⼀下单机如何优化到⾼性能,集群如何避免单点故障,保 证订单不超卖、不少卖的⼀些策略 完整的订单系统还有订单进度的查看,每台服务器上都有⼀个任务,定时的从总库存同步余票和库存信息展⽰给⽤⼾,还有 ⽤⼾在订单有效期内不⽀付,释放订单,补充到库存等等。 我们实现了⾼并发抢票的核⼼逻辑,可以说系统设计的⾮常的巧妙,巧妙的避开了对 DB 数据库 IO 的操作。 对 Redis ⽹络 IO 的⾼并发请求,⼏乎所有的计算都是在内存中完成的,⽽且有效的保证了不超卖、不少卖,还能够容忍部 分机器的宕机。 我觉得其中有两点特别值得学习总结: 11… 负负载载均均衡衡,,分分⽽⽽治治之之 通过负载均衡,将不同的流量划分到不同的机器上,每台机器处理好⾃⼰的请求,将⾃⼰的性能发挥到极致。 这样系统的整体也就能承受极⾼的并发了,就像⼯作的⼀个团队,每个⼈都将⾃⼰的价值发挥到了极致,团队成⻓⾃然是很 ⼤的。 22… 合合理理的的使使⽤⽤并并发发和和异异步步 ⾃ Epoll ⽹络架构模型解决了 c10k 问题以来,异步越来越被服务端开发⼈员所接受,能够⽤异步来做的⼯作,就⽤异步来 做,在功能拆解上能达到意想不到的效果。 这点在 Nginx、Node.JS、Redis 上都能体现,他们处理⽹络请求使⽤的 Epoll 模型,⽤实践告诉了我们单线程依然可以发 挥强⼤的威⼒。
服务器已经进⼊了多核时代,Go 语⾔这种天⽣为并发⽽⽣的语⾔,完美的发挥了服务器多核优势,很多可以并发处理的任 务都可以使⽤并发来解决,⽐如 Go 处理 HTTP 请求时每个请求都会在⼀个 Goroutine 中执⾏。 总之,怎样合理的压榨 CPU,让其发挥出应有的价值,是我们⼀直需要探索学习的⽅向。 -E N D- 如果看到这⾥,说明你喜欢这篇⽂章,请转转发发、、点点赞赞。微信搜索「web_resource」,关注后回复「进群」或者扫描下⽅⼆维码 即可进⼊⽆⼴告交流群。 ↓扫扫描描⼆⼆维维码码进进群群↓↓ 推荐阅读 11… 推荐⼀位⼤神,⼿握 GitHub 16000 star 22… 附源码!Spring Boot 并发登录⼈数控制 33… 为什么 Redis 单线程却能⽀撑⾼并发? 44… ⼲货!MySQL 数据库开发规范 55… 团队开发中 Git 最佳实践
声明:pdf仅供学习使⽤,⼀切版权归原创公众号所有;建议持续关注原创公众号获取最新⽂章,学习愉快! 学Java,请关注公众号:Java后端 喜欢⽂章,点个在在看看
RESTful 架构基础 ImportNew Java后端 2019-10-31 点击上⽅ Java后端,选择 设为星标 优质⽂章,及时送达 来⾃ | 唐尤华 译⾃ | dzone.com/refcardz/rest-foundations-restful 上篇 | 终于有⼈把 Docker 讲清楚了 REST(Representational State Transfer)架构⻛格是⼀种世界观,把信息提升为架构中的⼀等公⺠。 通过 REST 可以实现系 统的⾼性能、可伸缩、通⽤性、简单性、可修改性和可扩展等特性。这篇⽂章解释了主要的 HTTP 操作,对 HTTP 响应码进⾏描 述,并列举相关开发库和框架。此外,本⽂还提供了额外的资源,对每个主题进⾏了更深⼊的探讨。 11… 简简介介 REST 架构⻛格不是⼀种可以购买的技术,也不是⼀个可以添加到软件开发项⽬中的开发库。 ⾸先也是最重要的,REST 是⼀种世 界观,把将信息提升为构建架构中的⼀等公⺠。 Roy Fielding 的博⼠论⽂“架构⻛格和基于⽹络的软件架构设计”介绍和整理了“RESTful”系统的思想和相关术语。 这是⼀篇 学术论⽂,虽然使⽤正式语⾔,但是仍然易于理解并且提供了实践基础。 总结⼀下,RESTful 通过体系结构的特定选择能从部署的系统中获得理想特性。 尽管这种⻛格定义的约束细节并没有为所有场合 设计,但是的确可以⼴泛适⽤。 由于 Web 对消费者偏好有多重影响,REST ⻛格的倡导者⿎励企业组织在其边界内使⽤相同原则,就像他们在⾯向外部客⼾的⽹ ⻚上做的那样。本⽂将讨论现代 REST Web 实现中的基本约束和属性。 11…11 基基础础概概念念 REST 表⽰什么含义?以⽆状态⽅式传输、访问和操作⽂本数据。当正确部署后,REST 为互联⽹上不同应⽤程序之间提供了⼀致 的互操作性。⽆状态(stateless)这个术语⾄关重要,它使得应⽤程序可以⽤不可知的⽅式进⾏通信。RESTful API 通过统⼀资 源定位符地址(URL)公开服务。URL 名称将资源的区分为接受内容或返回内容。RFC 1738中定义了 URL scheme,可以在这 ⾥找到: https://tools.ietf.org/rfc/rfc1738.txt RESTful URL 类似于下⾯这个 library API: 实际公开的不⼀定是某种任意的服务,⽽是代表对消费者有价值的信息资源。URL 作为资源句柄,可以请求、更新或删除内容。 开始把服务发布到某个地⽅,然后开始与 REST 服务进⾏交互。 返回的内容可能是 XML、JSON 格式,或者更确切地说是像 Atom 或⾃定义 MIME 类型等超媒体格式。虽然⼀般建议尽可能重⽤现有的格式,但是对正确设计的媒体类型正在变得越来越宽 容。需要请求资源的时候,客⼾机会发⼀个超⽂本传输协议(HTTP)GET 请求,例如在浏览器中键⼊⼀个 URL 然后点击回⻋,选择 书签,或者点击锚引⽤链接。 通过编程⽅式与 RESTful API 交互,有数⼗个客⼾端 API 或⼯具可供选择。使⽤ curl 命令⾏⼯具,可以输⼊以下命令: 1 http://fakelibrary.org/library 1 $ curl http://fakelibrary.org/library
上⾯的命令使⽤默认格式,但你可能不需要这种格式的信息。幸运的是 HTTP 有⼀种机制,可以指定返回信息的格式。 在请求中 指定 “Accept” 头,如果服务器⽀持这种格式,会以指定的格式返回。 这个过程称为内容协商,这是 HTTP 中未被充分利⽤的功 能之⼀,可以使⽤⼀个类似于上⾯例⼦中的 curl 命令来指定: 由于资源名称与内容格式是独⽴的,从⽽让请求不同格式信息成为可能。虽然 REST 中的 “R” 的含义是 “表现”⽽⾮“资 源”,但是应该在构建系统时允许客⼾端指定请求的内容格式,请牢记这⼀点。在我们的例⼦中 library API 可能包含以下 URL:http://fakelibrary.org/library:图书馆基本信息,搜索图书、DVD等相关资源基本功能的链接。 http://fakelibrary.org/book:存放书籍的“信息空间”。从概念上说,这⾥可能会存放所有的书籍。显然,如果这个问题 得到解决,我们不会希望返回所有图书,⽽是希望通过类别、搜索关键词等来检索图书。 http://fakelibrary.org/book/category/1234:在书籍的信息空间⾥,我们可以指定类别浏览,例如成⼈⼩说、⼉童书 籍、园艺书籍等。使⽤杜威⼗进制图书分类法是可⾏的,但我们也可以想象⾃定义分组。问题的关键在于,这种“信息空 间”可能是⽆限的,⽽且可能收到⼈们实际关⼼的信息类型影响。 http://fakelibrary.org/book/isbn/978-0596801687:提到某本具体的书,应该包括书名、作者、出版商、系统中的拷⻉ 数、可⽤拷⻉数等信息。 译注:杜威⼗进制图书分类法由美国图书馆专家⻨尔威·杜威发明,于1876年⾸次发表,历经22次的⼤改版。该分类法以三位 数字代表分类码,共可分为10个⼤分类、100个中分类及1000个⼩分类。 就图书馆⽤⼾⽽⾔,上⾯提到的这些 URL 可能就是只读的,但是图书馆员使⽤应⽤程序时实际上可以操作这些资源。 例如添加⼀本新书,可以向 main/book 地址 POST ⼀个 XML。使⽤ curl 提交,看起来可能像这样: 此时,服务器可能会对提交的内容进⾏校验,创建与图书相关的记录,并返回响应代码201——表⽰已创建新资源。新资源的 URL 可以在响应的 Location 头中找到。 RESTful 请求⼀个重要特性:每次请求都包含了充⾜的状态信息来响应请求。这为服务器的可⻅性和⽆状态创造了条件,并为扩 展系统和识别发送的请求内容提供了理想特性。对于缓存结果也⾮常有帮助。服务器地址和请求状态组合成可计算的 hash 键 值,并形成⼀个结果集: 接下来我们会先介绍 GET 请求。客⼾端在需要时发出 GET 请求获取指定资源。客⼾端可以在本地缓存请求结果,服务器可以在 远程缓存结果,系统的中间层可以在请求链路中间缓存结果。这是⼀个与具体应⽤程序⽆关的特性,可以加⼊系统设计中。 正因为可以操作资源,也就意味着并不是每个⼈都可以这样做。我们完全可以建⽴⼀个防护模型,要求⽤⼾在操作前验证⾝份, 证明他们具有该操作的授权。在本⽂的最后,将提供⼀些提升 RESTful 服务安全性的内容。 22… RREESSTT 和和 SSOOAAPP ⽐⽐怎怎么么样样?? SOAP:简单对象访问协议(Simple Object Access Protocol)。是交换数据的⼀种协议规范,是⼀种轻量的、简单的、基于 XML的协议。⼀条 SOAP 消息就是⼀个普通的 XML ⽂档,包含必需的 Envelope 元素、可选的 Header 元素、必需的 Body 元 素和可选的 Fault 元素。 1 $ curl http://fakelibrary.org/library 1 $ curl –H “Accept:application/json” http://fakelibrary.org/library 1 $ curl –u username:password -d @book.xml -H “Content-type: text/xml” http://fakelibrary.org/book 1 http://fakelibrary.org + /book/isbn/978-0596801687
把 REST 与 SOAP 划等号是错误的。在这两者之间进⾏⽐较,带来的困扰远多于好处。简单来说,它们不是⼀回事。尽管可以⽤ 这两种⽅法解决许多架构问题,但是它们不能相互替换。 这种混淆很⼤程度上源于对“REST 是通过 URL 调⽤ Web 服务”这句话的误解。 这种观点与 RESTful 架构的功能相距甚远。如 果不全⾯深⼊理解 RESTful 的架构实现,就很容易误解 REST 实践的本意。 利⽤ REST 的最佳⽅式,是将⽣产和消费过程中的信息与技术分离实现解耦,进⽽更好地管理系统,让架构具备以下特性: ⾼性能 可扩展 通⽤简洁可修改 可扩展 这并不是说,基于 SOAP 构建的系统不能具备上述特性。 ⽽是当技术、组织或过程的复杂性造成不能在单个事务中完成请求的⽣ 命周期时,这种情况 SOAP 能够发挥最佳效果。 33… RRiicchhaarrddssoonn 成成熟熟度度模模型型 Leonard Richardson 引⼊了⼀种成熟度模型,部分阐述了 SOAP 与 REST 之间的区别,并提供⼀种对不同类型的系统进⾏分类 的框架。许多⼈不恰当地称之为 “REST”。 可以将这种分类看作系统中不同 Web 技术组件紧密程度的度量标准:包括信息资 源、HTTP 作为应⽤层协议和作超媒体作为控制媒介。 称其为“成熟度模型”似乎意味着应该只构建“成熟度”最⾼的系统。这种看法是不合适的。第2级是有价值的,从2级向3级转 变通常只是采⽤了⼀种新的 MIME 类型。然⽽,从0级到3级的转变要困难得多,因此增量式升级转变通常也会增值。 ⾸先,确定希望公开哪些信息资源。采⽤ HTTP 作为处理这些信息资源的应⽤协议,包括内容协商。 接下来,当⼀切就绪时,使 ⽤基于超媒体的 MIME 类型,这样就可以充分享受 REST 的好处了。 44… 动动词词 动词是⽤来与服务器资源交互的⽅法或操作。RESTful 系统中有限的动词让刚接触该的使⽤者感到困惑和沮丧。看似武断和不必 要的约束,⽬的是⿎励以应⽤程序⽆关的形式提供可预测的⾏为。通过明确、清晰地定义这些动词的⾏为,客⼾端可以在⽹络中 断或故障时⾃主处理。 精⼼设计的 RESTful 系统主要使⽤4个 HTTP 动词。 44…11 GGEETT
GET 请求是最常⽤的 Web 动词。GET 请求将命名资源从服务器传输到客⼾端。尽管客⼾端不需要知道请求的资源内容,但是请 求返回的结果是带元数据标记的字节流,这表明客⼾端应该知道如何解释资源。在 Web 中通常⽤ “text/html” 或 “application/xhtml+xml” 表⽰。 正如之前提到的那样,只要服务器⽀持,客⼾端可以通过内容协商提前指定请求的返回格 式。GET 请求关键点之⼀,不要修改服务器端的任何内容。 这是⼀个基本的安全要求,也是不熟悉 REST 的开发者犯的最⼤错误之 ⼀。你可能会遇到这样的 URL: 不不要要这这样样做做!! 由于 GET 请求安全性允许缓存请求,这会让正在构建的 RESTful 系统陷⼊混乱。 GET 请求也意味着幂等性,即多 次请求不会对系统产⽣任何影响。这是基于分布式基础设施的⼀个重要特性。如果进⾏ GET 请求时被打断,由于幂等性,客⼾端 可以再次发起请求。这点⾮常重要。在设计良好的基础结构中,客⼾端可以从任意应⽤程序发起请求。虽然⼀定会有与应⽤程序 相关的特定⾏为,但是加⼊与应⽤程序⽆关的⾏为越多,系统就会越有弹性,也更容易维护。 44…22 PPOOSSTT 在辨别 POST 和 PUT 动词意图的时候,情况开始变得不那么清晰。 根据定义,⼆者似乎都可以被客⼾端⽤来创建或更新服务器资 源,然⽽它们的⽤途各有不同。 当⽆法预测请求创建的资源的标识时,客⼾端会使⽤ POST 请求。 在新增雇员、下订单或提交表单的时候,我们⽆法预测服务器 将如何命名正在创建的资源。这就是为什么将资源提交给类似 Servlet 这样的程序处理。接下来,服务器会接受请求、校验请 求、验证⽤⼾凭据等。成功处理后,服务器将返回 201 HTTP 响应代码,其中包含⼀个 “Location” 头,代表新创建的资源的位 置。注注意意:: 有些⼈将 POST 视为创建资源的 GET 会话。他们会对创建的资源通过 body 返回200,⽽不是返回201。 这似乎是避免⼆ 次请求的⼀种快捷⽅式,但是这种做法混合了 POST 和 GET,让缓存资源的潜在影响变得微妙。 尽量避免因为⾛捷径⽽牺牲⼤ 局。短期看这似乎是值得的,但随着时间的推移,这些捷径叠加起来可能会带来不利的影响。 POST 动词的另⼀个主要⽤途是“追加(Append)”资源信息,即增量编辑或部分更新,⽽不是提交完整的资源。 这⾥应使⽤ PUT 操作。对已知资源使⽤ POST 更新,可⽤于向订单添加新送货地址或更新购物⻋中某个商品的数量。 由于是更新资源的部分信息,PPOOSSTT 既既不不安安全全也也不不幂幂等等。 POST 的最后⼀种常⻅⽤法是提交查询。将查询的内容或表单内容进⾏ URL 编码后提交给服务执⾏查询。通常可以直接返回 POST 结果,因为没有与查询相关的标识。 注注意意:: 建议将这样的查询转换为信息资源本⾝。如果采⽤ POST 查询,可以考虑采⽤ GET 请求,后者⽀持缓存。 你可以与其他 ⼈分享这个链接。 44…33 PPUUTT 由于 HTML 表单⽬前还不⽀持 PUT,许多开发⼈员基本上会忽略 PUT 动词。 然⽽,PUT 有⼀个重要作⽤并且是 RESTful 系统完 整愿景的⼀部分。 客⼾端可以向指定 URL 发 PUT 请求,服务器⽤请求中的数据执⾏覆盖操作。 PUT 请求在某种程度上是等幂的,⽽ POST 更新不 是。如果客⼾端在 PUT 覆盖请求时被打断,由于重新发送覆盖操不会造成任何后果,因此可以再次发送。 客⼾端具备管理状态能⼒, 所以直接重发覆盖命令即可。 1 http://example.com/res/action=update?data=1234
注注意意:: 这种协议层处理并不意味着要取消更⾼级别(如应⽤层)的事务,但是同样地,它也是⼀种体系结构上理想的属性,可以 在应⽤层以下使⽤。 如果客⼾端能够提前了解资源的标识,那么 PUT 也可⽤于创建资源。正如我们在 POST 部分中讨论的那样,通常不会出现这种情 况。但是如果客⼾端能够控制服务器端信息空间,那么这种操作也是合理的。 44…44 DDEELLEETTEE 在公共⽹络上 DELETE 动词没有被⼴泛使⽤(谢天谢地!)。 然⽽,对于控制信息空间⾮常有⽤,它是资源⽣命周期中⾮常有⽤的 ⼀部分。 DELETE 请求意在实现等幂。可能由于⽹络故障 DELETE 请求被打断,这时我们希望客⼾端继续尝试。 第⼀次请求⽆论成功与 否,资源都应该返回204(⽆指定内容)。对之前已删除的资源或不存在的资源可能需要⼀些额外处理,两种情况都应该返回 404。⼀些安全策略要求为不存在的和已删除的资源返回404,这样 DELETE 请求就不会泄漏有关资源是否存在的信息。 还有另外三个没有⼴泛使⽤但是有价值的动词。 44…55 HHEEAADD HEAD 动词⽤来请求资源,但不实际检索。客⼾端可以通过 HEAD 检查资源是否存在,并检查资源相关的元数据。 44…66 OOPPTTIIOONNSS OPTIONS 动词也可以⽤来查询服务器相关资源的情况,⽅法是询问哪些其它动词可⽤于该资源。 44…77 PPAATTCCHH 最新的动词 PATCH 直到2010年才正式采纳为 HTTP 的⼀部分。旨在提供⼀种标准化⽅式来表⽰部分更新。PATCH 请求通过标准 格式让交互的意图更明确。这是推荐使⽤ PATCH ⽽⾮ POST 的原因,尽管 POST 可以⽤于任何事情。 IETF 发布了 RFC ⽂档,定 义⽤于 PATCH 操作的 XML 和 JSON。 如果客⼾端 PATCH 请求的 header 中带 If-Match,则此部分为幂等更新。 可以重试中断的请求,因为如果第⼀次请求成功,那 么 If-Match header 会不同于新状态。如果相同,则未处理原始请求可应⽤ PATCH。 55… 响响应应码码 HTTP 响应码为我们在客⼾端和服务器之间的对话提供了丰富的请求状态信息。⼤多数⼈只熟悉⼀般意义上的200、403、404或 者500,但是还有更多有⽤的代码可供使⽤。这⾥表格并不全⾯,但是它们涵盖了许多在 RESTful 环境中应该考虑使⽤的最重要 代码。数字可按照以下类别分组: 1XX:信息类 2XX:操作成功 3XX:重定向 4XX:客⼾端错误 5XX:服务器错误 第⼀组响应码表明客⼾端的请求格式正确且处理成功。具体操作如下表所⽰:
表1 成功的客⼾端请求 表2 — 客⼾端重定向请求 表3中的响应代码表⽰客⼾端请求⽆效,如果条件不发⽣变化,重新请求仍⽆法处理。这些故障可能有请求格式错误、未授权的 请求、请求的资源不存在等。 表3 客⼾端请求错误 最后,表4中的响应代码表⽰服务器暂时⽆法处理客⼾端请求(可能仍然⽆效)。客⼾端应当在将来的某个时候重新请求。
表4 服务器处理请求错误 服务根据其⾃⾝功能要求具有不同程度的可扩展性。 注注意意:: 试试响应代码418,它会返回简洁有⼒的回复:“我是⼀个茶壶。” 55…11 RREESSTT 资资源源 55…11…11 论论⽂⽂ Fielding 博⼠的论⽂《架构的⻛格与基于⽹络的软件架构设计》是对 RESTful 思想的主要介 绍:http://www.ics.uci.edu/~fielding/pubs/dissertation/top.htm 55…11…22 RRFFCC 规规范范 REST 常⻅⽤法的技术规范由**国际互联⽹⼯程任务组(IETF)定义,按照请求评议(RFC)**流程完善。规范由数字定义,并随 着时间推移不时更新版本,以替换已经过时的⽂件。⽬前,这⾥有最新的相关 RFC ⽂件。 55…11…22…11 UURRII RFC 3986定义了 URI 命名⽅案的通⽤语法。URI 是⼀种命名⽅案,包含了对其他如⽹址、⽀持名字⼦空间等编码⽅案。 ⽹ 址:http://www.ietf.org/rfc/rfc3986.txt> 55…11…22…22 UURRLL Url 是 URI 的⼀种形式,其中嵌⼊了充⾜的信息(通常是访问⽅案和地址),⽤于解析和定位资源统⼀资源定位符。 ⽹ 址:http://www.ietf.org/rfc/rfc1738.txt 55…11…22…33 IIRRII 国际化资源标识符(IRI)在概念上是⼀个⽤ Unicode 编码的 URI,⽤于在 Web 上使⽤的标识符中⽀持世界上各种语⾔的字 符。IETF 选择创建⼀个新的标准,⽽不是改变 URI ⽅案本⾝,以避免破坏现有的系统并明确区分这两种⽅法。 那些⽀持 IRI 的⼈ 故意这样做。还定义了在 IRI 和 URI 之间进⾏转换的映射⽅案。⽹址<:http://www.ietf.org/rfc/rfc3987.txt 55…11…22…44 HHTTTTPP HTTP 1.1版本定义了⼀个应⽤程序协议,⽤于操作通常以超媒体格式表⽰的信息资源。 虽然它是⼀个应⽤级协议,但通常不与应 ⽤程序绑定,由此产⽣了重要的体系结构优势。⼤多数⼈认为 HTTP 和超⽂本标记语⾔⽂(HTML)就是“Web”,但是 HTTP 在⾮⾯向⽂档的系统开发中也很有⽤。⽹址:http://www.ietf.org/rfc/rfc2616.txt 55…11…22…55 PPAATTCCHH 格格式式 JavaScript 对象表⽰法(JSON)Patch ⽹址:https://www.ietf.org/rfc/rfc6902.txt XML Patch ⽹址:https://www.ietf.org/rfc/rfc7351.txt 55…22 描描述述语语⾔⾔
⼈们对使⽤各种语⾔来描述 API ⾮常感兴趣,通过描述语⾔可以更容易地编写客⼾端和服务器⽂档,甚⾄⽣成⻣架代码。 ⼀些⽐ 较流⾏、有趣的描述语⾔包括: 55…22…11 RRAAMMLL RAML 是⼀种 YAML/JSON 语⾔,可以定义2级成熟度的 API。 它⽀持可重⽤模式和特性,通过模式和特性实现功能 API 设计的标 准化。⽹址:http://raml.org 55…22…22 SSwwaaggggeerr Swagger 是另⼀种 YAML/JSON 语⾔,⽀持定义2级成熟度的 API。 它包含代码⽣成器、编辑器、API ⽂档可视化功能,能够与 其他服务集成的。⽹址:http://swagger.io 55…22…33 AAppiiaarryy…iioo Apiary.io 是⼀个协作式的托管站点。它⽀持 Markdown 格式的 API ⽂档,可以围绕设计过程进⾏社交,并且⽀持模拟数据的托 管实现,以便于在 API 实现之前对其进⾏测试。⽹址:http://apiary.io 55…22…44 HHyyddrraa–CCgg Hydra-Cg 是⼀种超媒体描述语⾔,通过像 JSON-LD 这样的标准⽅便地实现数据关联和并其它数据源的交互。 ⽹ 址:http://www.hydra-cg.com 55…33 实实现现 有⼀些⽤于构建、⽣成和使⽤ RESTful 系统的库和框架。 虽然任何 Web 服务器都可以配置成提供 REST API,但有了这些框架、 库和环境可以让过程变得更容易。 以下概述了⼀些主流的环境: 55…33…11 JJAAXX–RRSS JAX-RS 规范为 JEE 环境增加了对 REST 的⽀持。⽹址:https://jax-rs-spec.java.net 55…33…22 RReessttlleett Restlet API 是构建⽤于⽣产和消费 RESTful 系统的 Java API 先⾏者之⼀。它专注于为客⼾端和服务器⽣成⼀些⾮常⼲净、强⼤ 的 API。 Restlet Studio 是⼀个免费⼯具,能够在 RAML 和基于 swagger 的 API 描述之间进⾏转换,⽀持 Restlet、Node 和 JAX-RS 服 务器和客⼾端的⻣架和 Stub 代码。⽹址:http://restlet.org 55…33…33 NNeettKKeerrnneell Netkernel 是⼀个⽐较有趣的 RESTful 系统。它基于微内核,是⽀持各种架构⻛格环境的代表。Netkernel 受益于在软件体系结 构中采⽤ Web 的经济属性。你可以把它想象成“在内部引⼊ REST”。 虽然任何基于 REST 的系统在外⾯看起来都⼀样,但在运 ⾏环境内部 NetKernel 看起来也⼀样。⽹址:http://netkernel.org 55…33…44 PPllaayy 两个主要的 Scala REST 框架之⼀。⽹址:https://www.playframework.com
55…33…55 SSpprraayy 两个主要的 Scala REST 框架之⼀。它设计成配合 Akka actor 模型⼀起⼯作。⽹址:http://spray.io 55…33…66 EExxpprreessss 两个主要的 Node.js REST 框架之⼀。⽹址:http://expressjs.com 55…33…77 hhaappii 两个主要的 Node.js REST 框架之⼀。⽹址:http://hapijs.com 55…33…88 SSiinnaattrraa Sinatra 是⼀个领域特定语⾔(DSL),⽤来在 Ruby 中创建 RESTful 应⽤程序。⽹址:http://www.sinatrarb.com 55…44 客客⼾⼾端端 通过浏览器调⽤ REST API 是可⾏的,但是还有其它客⼾端可⽤于测试和构建⾯向资源的系统。 55…44…11 ccuurrll curl 是流⾏的库和命令⾏⼯具之⼀,⽀持在各种资源上调⽤各种协议。⽹址:https://curl.haxx.se 55…44…22 hhttttppiiee httpie 是⼀个⾮常灵活和易⽤的客⼾端,⽀持通过 HTTP 与资源进⾏交互。⽹址:https://httpie.org 55…44…33 PPoossttmmaann 健全的 API 测试需要能够捕获和重播请求,⽀持各种⾝份验证和授权⽅案等功能。 以前的命令⾏⼯具允许这样做,但 Postman 是⼀个较新的桌⾯应⽤程序,让这些⼯作对于开发团队来说变得更容易。⽹址:https://www.getpostman.com 66… 书书籍籍“RESTful Web APIs”:Leonard Richardson、Mike Amundsen 和 Sam Ruby,2013,O’Reilly 出版社 “RESTful Web Services Cookbook”:Subbu Allamaraju,2010,O’Reilly 出版社 “REST in Practice”: Jim Webber、Savas Parastatidis 和 Ian Robinson,2010,O’Reilly 出版社。 中⽂版《REST 实战(中⽂版)》 “Restlet in Action” by Jerome Louvel and Thierry Boileau,2011,Manning 出版社 “Resource-Oriented Architecture Patterns for Webs of Data (Synthesis Lectures on the Semantic Web: Theory and Technology)”:Brian Sletten,2013,Morgan & Claypool -E N D- 如果看到这⾥,说明你喜欢这篇⽂章,请转转发发、、点点赞赞。微信搜索「web_resource」,关注后回复「进群」或者扫描下⽅⼆维码 即可进⼊⽆⼴告交流群。 ↓扫扫描描⼆⼆维维码码进进群群↓↓
声明:pdf仅供学习使⽤,⼀切版权归原创公众号所有;建议持续关注原创公众号获取最新⽂章,学习愉快! 推荐阅读 11… 淘宝为什么能抗住双 11 ? 22… 为什么 ?阿⾥规定超过 3 张表禁⽌ join 33… 理解 IntelliJ IDEA 的项⽬配置和 Web 部署 44… Java 开发中常⽤的 4 种加密⽅法 55… 团队开发中 Git 最佳实践 学Java,请关注公众号:Java后端 喜欢⽂章,点个在在看看
SMM 架构如何做读写分离? Java后端 2019-12-07 点击上⽅ Java后端,选择 设为星标 优质⽂章,及时送达 来源 | http://1t.click/atjG 读写分离要做的事情就是对于⼀条SQL该选择哪个数据库去执⾏,⾄于谁来做选择数据库这件事⼉,⽆⾮两个,要么中间件帮我 们做,要么程序⾃⼰做。因此,⼀般来讲,读写分离有两种实现⽅式。 第⼀种是依靠中间件(⽐如:MyCat),也就是说应⽤程序连接到中间件,中间件帮我们做SQL分离;第⼆种是应⽤程序⾃⼰去 做分离。这⾥我们选择程序⾃⼰来做,主要是利⽤Spring提供的路由数据源,以及AOP 然⽽,应⽤程序层⾯去做读写分离最⼤的弱点(不⾜之处)在于⽆法动态增加数据库节点,因为数据源配置都是写在配置中的, 新增数据库意味着新加⼀个数据源,必然改配置,并重启应⽤。当然,好处就是相对简单。 ## AAbbssttrraaccttRRoouuttiinnggDDaattaaSSoouurrccee 基于特定的查找key路由到特定的数据源。它内部维护了⼀组⽬标数据源,并且做了路由key与⽬标数据源之间的映射,提供基于 key查找数据源的⽅法。
实实践践 maven依赖 <> 4.0.0 com.cjs.example cjs-datasource-demo 0.0.1-SNAPSHOT jar cjs-datasource-demo org.springframework.boot spring-boot-starter-parent 2.0.5.RELEASE <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> org.springframework.boot spring-boot-starter-aop org.springframework.boot spring-boot-starter-jdbc org.springframework.boot spring-boot-starter-web org.mybatis.spring.boot
/*** 关于数据源配置,参考SpringBoot官⽅⽂档第79章《Data Access》 * 79. Data Access * 79.1 Configure a Custom DataSource * 79.2 Configure Two DataSources /@@CCoonnffiigguurraattiioonn ppuubblliicc ccllaassss DataSourceConfig { @@BBeeaann @@CCoonnffiigguurraattiioonnPPrrooppeerrttiieess(“spring.datasource.master”) ppuubblliicc DataSource masterDataSource() { rreettuurrnn DataSourceBuilder.create().build(); } @@BBeeaann @@CCoonnffiigguurraattiioonnPPrrooppeerrttiieess(“spring.datasource.slave1”) ppuubblliicc DataSource slave1DataSource() { rreettuurrnn DataSourceBuilder.create().build(); } @@BBeeaann @@CCoonnffiigguurraattiioonnPPrrooppeerrttiieess(“spring.datasource.slave2”) ppuubblliicc DataSource slave2DataSource() { rreettuurrnn DataSourceBuilder.create().build(); } @@BBeeaann ppuubblliicc DataSource myRoutingDataSource(@@QQuuaalliiffiieerr(“masterDataSource”) DataSource masterDataSource, @@QQuuaalliiffiieerr(“slave1DataSource”) DataSource slave1DataSource, @@QQuuaalliiffiieerr(“slave2DataSource”) DataSource slave2DataSource) { Map<Object, Object> targetDataSources = nneeww HashMap<>(); targetDataSources.put(DBTypeEnum.MASTER, masterDataSource); targetDataSources.put(DBTypeEnum.SLAVE1, slave1DataSource); targetDataSources.put(DBTypeEnum.SLAVE2, slave2DataSource); MyRoutingDataSource myRoutingDataSource = nneeww MyRoutingDataSource(); myRoutingDataSource.setDefaultTargetDataSource(masterDataSource); myRoutingDataSource.setTargetDataSources(targetDataSources); rreettuurrnn myRoutingDataSource; } }这⾥,我们配置了4个数据源,1个master,2两个slave,1个路由数据源。前3个数据源都是为了⽣成第4个数据源,⽽且后续我 们只⽤这最后⼀个路由数据源。 MyBatis配置 @@EEnnaabblleeTTrraannssaaccttiioonnMMaannaaggeemmeenntt @@CCoonnffiigguurraattiioonn ppuubblliicc ccllaassss MMyyBBaattiissCCoonnffiigg { @@RReessoouurrccee(name = “myRoutingDataSource”) pprriivvaattee DataSource myRoutingDataSource; @@BBeeaann ppuubblliicc SqlSessionFactory ssqqllSSeessssiioonnFFaaccttoorryy() tthhrroowwss Exception { SqlSessionFactoryBean sqlSessionFactoryBean =nneeww SqlSessionFactoryBean(); sqlSessionFactoryBean.setDataSource(myRoutingDataSource); sqlSessionFactoryBean.setMapperLocations(nneeww PathMatchingResourcePatternResolver().getResources("classpath:mapper/.xml")); rreettuurrnn sqlSessionFactoryBean.getObject(); } @@BBeeaann ppuubblliicc PlatformTransactionManager ppllaattffoorrmmTTrraannssaaccttiioonnMMaannaaggeerr() { rreettuurrnn nneeww DataSourceTransactionManager(myRoutingDataSource); } }由于Spring容器中现在有4个数据源,所以我们需要为事务管理器和MyBatis⼿动指定⼀个明确的数据源。
设置路由key / 查找数据源 ⽬标数据源就是那前3个这个我们是知道的,但是使⽤的时候是如果查找数据源的呢? ⾸先,我们定义⼀个枚举来代表这三个数据源 ppaacckkaaggee com.cjs.example.enums; ppuubblliicc eennuumm DBTypeEnum { MASTER, SLAVE1, SLAVE2; }接下来,通过ThreadLocal将数据源设置到每个线程上下⽂中 ppuubblliicc ccllaassss DDBBCCoonntteexxttHHoollddeerr { pprriivvaattee ssttaattiicc final ThreadLocal
@Aspect @Component public class DataSourceAop { @Pointcut("!@annotation(com.cjs.example.annotation.Master) " + “&& (execution(* com.cjs.example.service….select(…)) " + “|| execution(* com.cjs.example.service….get(…)))”) public void readPointcut() { } @Pointcut(”@annotation(com.cjs.example.annotation.Master) " + "|| execution(* com.cjs.example.service….insert(…)) " + "|| execution(* com.cjs.example.service….add(…)) " + "|| execution(* com.cjs.example.service….update(…)) " + "|| execution(* com.cjs.example.service….edit(…)) " + “|| execution(* com.cjs.example.service….delete(…)) " + “|| execution(* com.cjs.example.service….remove(…))”) public void writePointcut() { } @Before(“readPointcut()”) public void read() { DDBBCCoonntteexxttHHoollddeerr.slave(); } @BBeeffoorree(“writePointcut()”) ppuubblliicc vvooiidd wwrriittee() { DDBBCCoonntteexxttHHoollddeerr.master(); } /** * 另⼀种写法:if…else… 判断哪些需要读从数据库,其余的⾛主数据库 / // @Before("execution( com.cjs.example.service.impl..(…))”) // public void before(JoinPoint jp) { // String methodName = jp.getSignature().getName(); //// if (StringUtils.startsWithAny(methodName, “get”, “select”, “find”)) { // DBContextHolder.slave(); // }else { // DBContextHolder.master(); // }// }}有⼀般情况就有特殊情况,特殊情况是某些情况下我们需要强制读主库,针对这种情况,我们定义⼀个主键,⽤该注解标注的就 读主库 ppaacckkaaggee ccoomm.cjs.example.annotation; ppuubblliicc @iinntteerrffaaccee Master { }例如,假设我们有⼀张表member
@@SSeerrvviiccee ppuubblliicc ccllaassss MMeemmbbeerrSSeerrvviicceeIImmppll iimmpplleemmeennttss MMeemmbbeerrSSeerrvviiccee { @@AAuuttoowwiirreedd pprriivvaattee MemberMapper memberMapper; @@TTrraannssaaccttiioonnaall @@OOvveerrrriiddee ppuubblliicc iinntt iinnsseerrtt(Member member) { rreettuurrnn memberMapper.insert(member); } @@MMaasstteerr @@OOvveerrrriiddee ppuubblliicc iinntt ssaavvee(Member member) { rreettuurrnn memberMapper.insert(member); } @@OOvveerrrriiddee ppuubblliicc List
⼯⼯程程结结构构
【END】 如果看到这⾥,说明你喜欢这篇⽂章,请转转发发、、点点赞赞。微信搜索「web_resource」,关注后回复「进群」或者扫描下⽅⼆维码 即可进⼊⽆⼴告交流群。 ↓扫扫描描⼆⼆维维码码进进群群↓↓
声明:pdf仅供学习使⽤,⼀切版权归原创公众号所有;建议持续关注原创公众号获取最新⽂章,学习愉快! 推荐阅读 11… Spring Boot:启动原理解析 22… ⼀键下载 Pornhub 视频! 33… Spring Boot 多模块项⽬实践(附打包⽅法) 44… ⼀个⼥⽣不主动联系你还有机会吗? 55… 团队开发中 Git 最佳实践 喜欢⽂章,点个在在看看
Spring Boot + Vue 如此强⼤?竟然可以开发基于 C/S 架构的应⽤ Java后端 2⽉15⽇ 作者 | xiangzhihong segmentfault.com/a/1190000021376934 前前⾔⾔虽然 B/S 是⽬前开发的主流,但是 C/S 仍然有很⼤的市场需求。受限于浏览器的沙盒限制,⽹⻚应⽤⽆法满⾜某些场景 下的使⽤需求,⽽桌⾯应⽤可以读写本地⽂件、调⽤更多系统资源,再加上 Web 开发的低成本、⾼效率的优势,这种跨 平台⽅式越来越受到开发者的喜爱。 Electron 是⼀个基于 Chromium 和 Node.js,使⽤ HTML、CSS 和 JavaScript 来构建跨平台应⽤的跨平台开发框架, 兼容 Mac、Windows 和 Linux。⽬前,Electron 已经创建了包括 VScode 和 Atom 在内的⼤量应⽤。 环环境境搭搭建建 创建 Electron 跨平台应⽤之前,需要先安装⼀些常⽤的⼯具,如 Node、vue 和 Electron 等。 安安装装 NNooddee 进⼊ Node 官⽹下载⻚ http://nodejs.cn/download/,然后下载对应的版本即可,下载时建议下载稳定版本。如果安装 Node 使⽤ Homebrew ⽅式,建议安装时将 npm 仓库镜像改为淘宝镜像,如下所⽰。 npm config set registry http://registry.npm.taobao.org/ 或者npm install -g cnpm --registry=https://registry.npm.taobao.org 安安装装//升升级级 vvuuee–ccllii 先执⾏以下命令,确认下本地安装的 vue-cli 版本。 vue -V 如果没有安装或者不是最新版,可以执⾏以下命令安装/升级。 npm install @vue/cli -g 安安装装 EElleeccttrroonn 使⽤如下命令安装 Electron 插件。
npm install -g electron 或者cnpm install -g electron 为了验证是否安装成功,可以使⽤如下的命令。 electron --version 创创建建运运⾏⾏项项⽬⽬ Electron 官⽅提供了⼀个简单的项⽬,可以执⾏以下命令将项⽬克隆到本地。 git clone https://github.com/electron/electron-quick-start 然后在项⽬中执⾏如下命令即可启动项⽬。 cd electron-quick-start npm install npm start 启动后项⽬的效果如下图。 除此之外,我们可以使⽤ vue-cli 脚⼿架⼯具来创建项⽬。 vue init simulatedgreg/electron-vue 然后根据下⾯的提⽰⼀步步选中选项即可创建项⽬,如下所⽰。 然后,使⽤ npm install 命令安装项⽬所需要的依赖包,安装完成之后,可以使⽤ npm run dev 或 npm run build 命令 运⾏ electron-vue 模版应⽤程序,运⾏效果如下图所⽰。
EElleeccttrroonn 源源码码⽬⽬录录 Electron 的源代码主要依据 Chromium 的拆分约定被拆成了许多部分。为了更好地理解源代码,您可能需要了解⼀下 Chromium 的多进程架构。 Electron 源码⽬录结构和含义具体如下: Electron ├──atom - Electron 的源代码 | ├── app - 系统⼊⼝代码 | ├── browser - 包含了主窗⼝、UI 和其他所有与主进程有关的东西,它会告诉渲染进程如何管理⻚⾯ | | ├── lib - 主进程初始化代码中 JavaScript 部分的代码 | | ├── ui - 不同平台上 UI 部分的实现 | | | ├── cocoa - Cocoa 部分的源代码 | | | ├── gtk - GTK+ 部分的源代码 | | | └── win - Windows GUI 部分的源代码 | | ├── default_app - 在没有指定 app 的情况下 Electron 启动时默认显⽰的⻚⾯ | | ├── api - 主进程 API 的实现 | | | └── lib - API 实现中 Javascript 部分的代码 | | ├── net - ⽹络相关的代码 | | ├── mac - 与 Mac 有关的 Objective-C 代码 | | └── resources - 图标,平台相关的⽂件等 | ├── renderer - 运⾏在渲染进程中的代码 | | ├── lib - 渲染进程初始化代码中 JavaScript 部分的代码 | | └── api - 渲染进程 API 的实现 | | └── lib - API 实现中 Javascript 部分的代码 | └── common - 同时被主进程和渲染进程⽤到的代码,包括了⼀些⽤来将 node 的事件循环 | | 整合到 Chromium 的事件循环中时⽤到的⼯具函数和代码 | ├── lib - 同时被主进程和渲染进程使⽤到的 Javascript 初始化代码 | └── api - 同时被主进程和渲染进程使⽤到的 API 的实现以及 Electron 内置模块的基础设施 | └── lib - API 实现中 Javascript 部分的代码 ├── chromium_src - 从 Chromium 项⽬中拷⻉来的代码 ├── docs - 英语版本的⽂档 ├── docs-translations - 各种语⾔版本的⽂档翻译 ├── spec - ⾃动化测试 ├── atom.gyp - Electron 的构建规则 └── common.gypi - 为诸如 node
和 breakpad
等其他组件准备的编译设置和构建规则 平时开发时,需要重点关注的就是 src、package.json 和 appveyor.yml ⽬录。除此之外,其他需要注意的⽬录如下:
script - ⽤于诸如构建、打包、测试等开发⽤途的脚本 tools - 在 gyp ⽂件中⽤到的⼯具脚本,但与 script ⽬录不同, 该⽬录中的脚本不应该被⽤⼾直接调⽤ vendor - 第三⽅依赖项的源代码,为了防⽌⼈们将它与 Chromium 源码中的同名⽬录相混淆,在这⾥我们不使⽤ third_party 作为⽬录名 node_modules - 在构建中⽤到的第三⽅ node 模块 out - ninja 的临时输出⽬录 dist - 由脚本 script/create-dist.py 创建的临时发布⽬录 external_binaries - 下载的不⽀持通过 gyp 构建的预编译第三⽅框架 应应⽤⽤⼯⼯程程⽬⽬录录 使⽤ electron-vue 模版创建的 Electron ⼯程结构如下图。
和前端⼯程的项⽬结构类似,Electron 项⽬的⽬录结构如下所⽰: electron-vue:Electron模版配置。 build:⽂件夹,⽤来存放项⽬构建脚本。 config:中存放项⽬的⼀些基本配置信息,最常⽤的就是端⼝转发。 node_modules:这个⽬录存放的是项⽬的所有依赖,即 npm install 命令下载下来的⽂件。 src:这个⽬录下存放项⽬的源码,即开发者写的代码放在这⾥。 static:⽤来存放静态资源。 index.html:则是项⽬的⾸⻚、⼊⼝⻚,也是整个项⽬唯⼀的HTML⻚⾯。 package.json:中定义了项⽬的所有依赖,包括开发时依赖和发布时依赖。 对于开发者来说, 90% 的⼯作都是在 src 中完成,src 中的⽂件⽬录如下。
Electron 应⽤程序分成三个基础模块:主进程、进程间通信和渲染进程。 11、、主主进进程程 Electron 运⾏ package.json 的 main 脚本(background.js)的进程被称为主进程。在主进程中运⾏的脚本通过创建 web⻚⾯来展⽰⽤⼾界⾯。⼀个 Electron 应⽤总是有且只有⼀个主进程。 22、、渲渲染染进进程程 由于 Electron 使⽤了 Chromium 来展⽰ Web ⻚⾯,所以 Chromium 的多进程架构也被使⽤到。每个 Electron 中的 Web ⻚⾯运⾏在它⾃⼰的渲染进程中。在普通的浏览器中,Web ⻚⾯通常在⼀个沙盒环境中运⾏,不被允许去接触原⽣ 的资源。然⽽ Electron 允许⽤⼾在 Node.js 的 API ⽀持下可以在⻚⾯中和操作系统进⾏⼀些底层交互。 33、、主主进进程程与与渲渲染染进进程程通通信信 主进程使⽤ BrowserWindow 实例创建⻚⾯。每个 BrowserWindow 实例都在⾃⼰的渲染进程⾥运⾏⻚⾯。当⼀个 BrowserWindow 实例被销毁后,相应的渲染进程也会被终⽌。主进程管理所有的 Web ⻚⾯和它们对应的渲染进程。每 个渲染进程都是独⽴的,它只关⼼它所运⾏的 Web ⻚⾯。 ssrrcc ⽬⽬录录结结构构 在 Electron ⽬录中,src 会包包含 main 和 renderer 两个⽬录。 mmaaiinn ⽬⽬录录 main ⽬录会包含 index.js 和 index.dev.js 两个⽂件。 index.js:应⽤程序的主⽂件,electron 也从这⾥启动的,它也被⽤作 webpack 产品构建的⼊⼝⽂件,所有的 main 进程⼯作都应该从这⾥开始。 index.dev.js:此⽂件专⻔⽤于开发阶段,因为它会安装 electron-debug 和 vue-devtools。 ⼀般不需要修改此⽂ 件,但它可以扩展开发的需求。 渲渲染染进进程程 renderer 是渲染进程⽬录,平时项⽬开发源码的存放⽬录,包含 assets、components、router、store、App.vue 和 main.js。 assets:assets 下的⽂件如(js、css)都会在 dist ⽂件夹下⾯的项⽬⽬录分别合并到⼀个⽂件⾥⾯ 去。components:此⽂件⽤于存放应⽤开发的组件,可以是⾃定义的组件。router:如果你了解 vue-router,那么 Electron 项⽬的路由的使⽤⽅式和 vue-router 的使⽤⽅式类似。modules:electron-vue 利⽤ vuex 的模块结构创建多 个数据存储,并保存在 src/renderer/store/modules 中。 相相关关案案例例 https://github.com/xiaozhu188/electron-vue-cloud-music
https://github.com/SmallRuralDog/electron-vue-music 推荐阅读 11… 为为了了去去阿阿⾥⾥,,我我准准备备了了⼀⼀年年, 没没想想到到竟竟是是这这样样的的结结果果 22… ⼤⼤⽩⽩话话带带你你梳梳理理⼀⼀下下 DDuubbbboo 的的那那些些事事⼉⼉ 33… 安安利利⼀⼀款款 IIDDEEAA 中中强强⼤⼤的的代代码码⽣⽣成成利利器器
声明:pdf仅供学习使⽤,⼀切版权归原创公众号所有;建议持续关注原创公众号获取最新⽂章,学习愉快! 44… 如如何何获获取取靠靠谱谱的的新新型型冠冠状状病病毒毒疫疫情情
Spring Security 架构简介 2019-11-26 以下⽂章来源于全栈修仙之路 ,作者semlinker Java后端 全全栈栈修修仙仙之之路路 聚焦全栈,专注分享 Angular、TypeScript、Node.js/Java 、Spring 技术栈等全栈⼲货。 ⼀⼀、、技技术术概概述述 11…11 SSpprriinngg vvss SSpprriinngg BBoooott vvss SSpprriinngg SSeeccuurriittyy 11…11…11 SSpprriinngg FFrraammeewwoorrkk SSpprriinngg FFrraammeewwoorrkk 为开发 Java 应⽤程序提供了全⾯的基础架构⽀持。它包含了⼀些不错的功能,如 “依赖注⼊”,以及 ⼀些现成的模块: Spring JDBC Spring MVC Spring Security Spring AOP Spring ORM 这些模块可以⼤⼤减少应⽤程序的开发时间。例如,在 Java Web 开发的早期,我们需要编写⼤量样板代码以将记录插⼊数 据源。但是,通过使⽤ Spring JDBC 模块的JJDDBBCCTTeemmppllaattee,我们可以仅通过少量配置将其简化为⼏⾏代码。 阅阅读读更更多多关关于于 SSpprriinngg BBoooott、、JJaavvaa 、、SSpprriinngg全全家家桶桶 等等技技术术⽂⽂章章,,欢欢迎迎关关注注微微信信公公众众号号::JJaavvaa后后端端 11…11…22 SSpprriinngg BBoooott SSpprriinngg BBoooott 是基于 Spring Framework,它为你的 Spring 应⽤程序提供了⾃动装配特性,它的设计⽬标是让你尽可能 快的上⼿应⽤程序的开发。以下是 Spring Boot 所拥有的⼀些特性: 可以创建独⽴的 Spring 应⽤程序,并且基于 Maven 或 Gradle 插件,可以创建可执⾏的 JARs 和 WARs; 内嵌 Tomcat 或 Jetty 等 Servlet 容器; 提供⾃动配置的 “starter” 项⽬对象模型(POMS)以简化 Maven 配置; 尽可能⾃动配置 Spring 容器; 提供⼀些常⻅的功能、如监控、WEB容器,健康,安全等功能; 绝对没有代码⽣成,也不需要 XML 配置。 11…11…33 SSpprriinngg SSeeccuurriittyy SSpprriinngg SSeeccuurriittyy 是⼀个能够为基于 Spring 的企业应⽤系统提供声明式的安全访问控制解决⽅案的安全框架。它提供了 ⼀组可以在 Spring 应⽤上下⽂中配置的 Bean,充分利⽤了 Spring IoC(Inversion of Control 控制反 转),D(I Dependency Injection 依赖注⼊)和 AOP(⾯向切⾯编程)功能,为应⽤系统提供声明式的安全访问控制功能, 减少了为企业系统安全控制编写⼤量重复代码的⼯作。
Spring Security 拥有以下特性: 对⾝份验证和授权的全⾯且可扩展的⽀持 防御会话固定、点击劫持,跨站请求伪造等攻击 ⽀持 Servlet API 集成 ⽀持与 Spring Web MVC 集成 Spring、Spring Boot 和 Spring Security 三者的关系如下图所⽰: 11…22 SSpprriinngg SSeeccuurriittyy 集集成成 ⽬前 Spring Security 5 ⽀持与以下技术进⾏集成: HTTP basic access authentication LDAP system OpenID identity providers JAAS API CAS Server ESB Platform … Your own authentication system 在进⼊ Spring Security 正题之前,我们先来了解⼀下它的整体架构:
⼆⼆、、核核⼼⼼组组件件 22…11 SSeeccuurriittyyCCoonntteexxttHHoollddeerr,,SSeeccuurriittyyCCoonntteexxtt 和和 AAuutthheennttiiccaattiioonn 最基本的对象是 SecurityContextHolder,它是我们存储当前应⽤程序安全上下⽂的详细信息,其中包括当前使⽤应⽤程 序的主体的详细信息。如当前操作的⽤⼾是谁,该⽤⼾是否已经被认证,他拥有哪些⻆⾊权限等。 默认情况下,SecurityContextHolder 使⽤ ThreadLocal 来存储这些详细信息,这意味着 Security Context 始终可⽤ 于同⼀执⾏线程中的⽅法,即使 Security Context 未作为这些⽅法的参数显式传递。 获获取取当当前前⽤⽤⼾⼾的的信信息息 因为⾝份信息与当前执⾏线程已绑定,所以可以使⽤以下代码块在应⽤程序中获取当前已验证⽤⼾的⽤⼾名: Object principal = SecurityContextHolder.getContext() .getAuthentication().getPrincipal(); iiff (principal iinnssttaanncceeooff UserDetails) { String username = ((UserDetails)principal).getUsername(); } eellssee { String username = principal.toString(); }调⽤ getContext() 返回的对象是 SecurityContext 接⼝的⼀个实例,对应 SecurityContext 接⼝定义如下:
ppuubblliicc iinntteerrffaaccee SSeeccuurriittyyCCoonntteexxtt eexxtteennddss SSeerriiaalliizzaabbllee { Authentication ggeettAAuutthheennttiiccaattiioonn(); vvooiidd sseettAAuutthheennttiiccaattiioonn(Authentication authentication); }Authentication 在 SecurityContext 接⼝中定义了 getAuthentication 和 setAuthentication 两个抽象⽅法,当调⽤ getAuthentication ⽅法后会返回⼀个 Authentication 类型的对象,这⾥的 Authentication 也是⼀个接⼝,它的定义 如下: ppuubblliicc iinntteerrffaaccee AAuutthheennttiiccaattiioonneexxtteennddss PPrriinncciippaall, SSeerriiaalliizzaabbllee { Collection<? extends GrantedAuthority> getAuthorities(); Object ggeettCCrreeddeennttiiaallss(); Object ggeettDDeettaaiillss(); Object ggeettPPrriinncciippaall(); bboooolleeaann iissAAuutthheennttiiccaatteedd(); vvooiidd sseettAAuutthheennttiiccaatteedd(bboooolleeaann isAuthenticated) tthhrroowwss IllegalArgumentException; }以上的 Authentication 接⼝是 spring-security-core jar 包中的接⼝,直接继承⾃ Principal 类,⽽ Principal 是位于 java.security 包中,由此可知 Authentication 是 spring security 中核⼼的接⼝。通通过过这这个个 AAuutthheennttiiccaattiioonn 接接⼝⼝的的 实实现现类类,,我我们们可可以以得得到到⽤⽤⼾⼾拥拥有有的的权权限限信信息息列列表表,,密密码码,,⽤⽤⼾⼾细细节节信信息息,,⽤⽤⼾⼾⾝⾝份份信信息息,,认认证证信信息息等等。。 22…22 ⼩⼩结结 下⾯我们来简单总结⼀下 SecurityContextHolder,SecurityContext 和 Authentication 这个三个对象之间的关 系,SecurityContextHolder ⽤来保存 SecurityContext(安全上下⽂对象),通过调⽤ SecurityContext 对象中的⽅ 法,如 getAuthentication ⽅法,我们可以⽅便地获取 Authentication 对象,利⽤该对象我们可以进⼀步获取已认证⽤ ⼾的详细信息。 SecurityContextHolder,SecurityContext 和 Authentication 的详细定义如下所⽰: 三三、、⾝⾝份份验验证证 33…11 SSpprriinngg SSeeccuurriittyy 中中的的⾝⾝份份验验证证是是什什么么?? 让我们考虑⼀个每个⼈都熟悉的标准⾝份验证⽅案:
系统会提⽰⽤⼾使⽤⽤⼾名和密码登录。 系统验证⽤⼾名和密码是否正确。 若验证通过则获取该⽤⼾的上下⽂信息(如权限列表)。 为⽤⼾建⽴安全上下⽂。 ⽤⼾继续进⾏,可能执⾏某些操作,该操作可能受访问控制机制的保护,该访问控制机制根据当前安全上下⽂信息检查 操作所需的权限。 前三项构成了⾝份验证进程,因此我们将在 Spring Security 中查看这些内容。 获 取 ⽤ ⼾ 名 和 密 码 并 将 其 组 合 到 UsernamePasswordAuthenticationToken 的实例中(我们之前看到 的Authentication 接⼝的实例)。 令牌将传递给 AuthenticationManager 的实例以进⾏验证。 AuthenticationManager 在成功验证时返回完全填充的 Authentication 实例。 SecurityContext 对象是通过调⽤ SecurityContextHolder.getContext().setAuthentication(…) 创建的,传 ⼊返回的⾝份验证 Authentication 对象。 33…22 SSpprriinngg SSeeccuurriittyy ⾝⾝份份验验证证流流程程⽰⽰例例 了解完上述的⾝份验证流程,我们来看⼀个简单的⽰例: AAuutthheennttiiccaattiioonnMMaannaaggeerr 接接⼝⼝:: ppuubblliicc iinntteerrffaaccee AAuutthheennttiiccaattiioonnMMaannaaggeerr { Authentication aauutthheennttiiccaattee(Authentication authentication) tthhrroowwss AuthenticationException; }SSaammpplleeAAuutthheennttiiccaattiioonnMMaannaaggeerr 类类:: ccllaassss SSaammpplleeAAuutthheennttiiccaattiioonnMMaannaaggeerr iimmpplleemmeennttss AAuutthheennttiiccaattiioonnMMaannaaggeerr { ssttaattiicc ffiinnaall List
ppuubblliicc ccllaassss AAuutthheennttiiccaattiioonnEExxaammppllee { pprriivvaattee ssttaattiicc AuthenticationManager am = nneeww SampleAuthenticationManager(); ppuubblliicc ssttaattiicc vvooiidd mmaaiinn(String[] args) throws Exception { BufferedReader iinn = nneeww BufferedReader(nneeww InputStreamReader(System.iinn)); wwhhiillee(true) { System.oouutt.println(“Please enter your username:”); String name = iinn.readLine(); System.oouutt.println(“Please enter your password:”); String password =iinn.readLine(); ttrryy { Authentication request = nneeww UsernamePasswordAuthenticationToken(name, password); Authentication result = am.authenticate(request); SecurityContextHolder.getContext().setAuthentication(result); bbrreeaakk; } ccaattcchh(AuthenticationException e) { System.oouutt.println("Authentication failed: " + e.getMessage()); }}System.oouutt.println("Successfully authenticated. Security context contains: " + SecurityContextHolder.getContext().getAuthentication()); }}在以上代码中,我们实现的 AuthenticationManager 将验证⽤⼾名和密码相同的任何⽤⼾。它为每个⽤⼾分配⼀个⻆ ⾊。上⾯代码的验证过程是这样的: Please enter your username: semlinker Please enter your password: 12345 Authentication failed: Bad Credentials Please enter your username: semlinker Please enter your password: semlinker Successfully authenticated. Security context contains: org.springframework.security.authentication.UsernamePasswordAuthenticationToken@ 四四、、核核⼼⼼服服务务 44…11 AAuutthheennttiiccaattiioonnMMaannaaggeerr,,PPrroovviiddeerrMMaannaaggeerr 和和 AAuutthheennttiiccaattiioonnPPrroovviiddeerr AuthenticationManager(接⼝)是认证相关的核⼼接⼝,也是发起认证的出发点,因为在实际需求中,我们可能会允许⽤ ⼾使⽤⽤⼾名 + 密码登录,同时允许⽤⼾使⽤邮箱 + 密码,⼿机号码 + 密码登录,甚⾄,可能允许⽤⼾使⽤指纹登录,所以 要求认证系统要⽀持多种认证⽅式。 Spring Security 中 AuthenticationManager 接⼝的默认实现是PPrroovviiddeerrMMaannaaggeerr,但它本⾝并不直接处理⾝份验 证请求,它会委托给已配置的 AAuutthheennttiiccaattiioonnPPrroovviiddeerr 列表,每个列表依次被查询以查看它是否可以执⾏⾝份验证。每 个 Provider 验证程序将抛出异常或返回⼀个完全填充的 AAuutthheennttiiccaattiioonn 对象。 也就是说,Spring Security 中核⼼的认证⼊⼝始终只有⼀个:AuthenticationManager,不同的认证⽅式:⽤⼾名 + 密 码(UsernamePasswordAuthenticationToken),邮箱 + 密码,⼿机号码 + 密码登录则对应了三个
AuthenticationProvider。 下⾯我们来看⼀下 PPrroovviiddeerrMMaannaaggeerr 的核⼼源码: ppuubblliicc ccllaassssPPrroovviiddeerrMMaannaaggeerr iimmpplleemmeennttss AAuutthheennttiiccaattiioonnMMaannaaggeerr, MMeessssaaggeeSSoouurrcceeAAwwaarree, IInniittiiaalliizziinnggBBeeaann { pprriivvaattee List
iiff (parentResult == nnuullll) { eventPublisher.publishAuthenticationSuccess(result); }rreettuurrnn result; }iiff (lastException == nnuullll) { lastException = nneeww ProviderNotFoundException(messages.getMessage( “ProviderManager.providerNotFound”, nneeww Object[] { toTest.getName() }, “No AuthenticationProvider found for {0}”)); }iiff (parentException == nnuullll) { prepareException(lastException, authentication); }tthhrrooww lastException; } }在 ProviderManager 进⾏认证的过程中,会遍历 providers 列表,判断是否⽀持当前 authentication 对象的认证⽅ 式,若⽀持该认证⽅式时,就会调⽤所匹配 provider(AuthenticationProvider)对象的 authenticate ⽅法进⾏认证操 作。若认证失败则返回 null,下⼀个 AuthenticationProvider 会继续尝试认证,如果所有认证器都⽆法认证成功,则 ProviderManager 会抛出⼀个 ProviderNotFoundException 异常。 44…22 DDaaooAAuutthheennttiiccaattiioonnPPrroovviiddeerr 在 Spring Security 中较常⽤的 AuthenticationProvider 是 DaoAuthenticationProvider,这也是 Spring Security 最早⽀持的 AuthenticationProvider 之⼀。顾名思义,Dao 正是数据访问层的缩写,也暗⽰了这个⾝份认证器 的实现思路。DaoAuthenticationProvider 类的内部结构如下: 在实际项⽬中,最常⻅的认证⽅式是使⽤⽤⼾名和密码。⽤⼾在登录表单中提交了⽤⼾名和密码,⽽对于已注册的⽤⼾,在 数据库中已保存了正确的⽤⼾名和密码,认证便是负责⽐对同⼀个⽤⼾名,提交的密码和数据库中所保存的密码是否相同 便是了。 在 Spring Security 中,对于使⽤⽤⼾名和密码进⾏认证的场景,⽤⼾在登录表单中提交的⽤⼾名和密码,被封装成了 UsernamePasswordAuthenticationToken,⽽根据⽤⼾名加载⽤⼾的任务则是交给了 UserDetailsService,在 DaoAuthenticationProvider 中,对应的⽅法就是 retrieveUser,虽然有两个参数,但是 retrieveUser 只有第⼀个参 数起主要作⽤,返回⼀个 UserDetails。retrieveUser ⽅法的具体实现如下:
pprrootteecctteedd ffiinnaall UserDetails rreettrriieevveeUUsseerr(String username, UsernamePasswordAuthenticationToken authentication) tthhrroowwss AuthenticationException { prepareTimingAttackProtection(); ttrryy { UserDetails loadedUser =tthhiiss.getUserDetailsService().loadUserByUsername(username); iiff (loadedUser == nnuullll) { tthhrrooww nneeww InternalAuthenticationServiceException( “UserDetailsService returned null, which is an interface contract violation” ); }rreettuurrnn loadedUser; }ccaattcchh (UsernameNotFoundException ex) { mitigateAgainstTimingAttack(authentication); tthhrrooww ex; }ccaattcchh (InternalAuthenticationServiceException ex) { tthhrrooww ex; }ccaattcchh (Exception ex) { tthhrrooww nneeww InternalAuthenticationServiceException(ex.getMessage(), ex); } }在 DaoAuthenticationProvider 类的 retrieveUser ⽅法中,会以传⼊的 username 作为参数,调⽤ UserDetailsService 对象的 loadUserByUsername ⽅法加载⽤⼾。 44…33 UUsseerrDDeettaaiillss 与与 UUsseerrDDeettaaiillssSSeerrvviiccee 44…33…11 UUsseerrDDeettaaiillss 接接⼝⼝ 在 DaoAuthenticationProvider 类中 retrieveUser ⽅法签名是这样的: pprrootteecctteedd ffiinnaall UserDetails rreettrriieevveeUUsseerr(String username, UsernamePasswordAuthenticationToken authentication) tthhrroowwss AuthenticationException { }该⽅法返回 UserDetails 对象,这⾥的 UserDetails 也是⼀个接⼝,它的定义如下: ppuubblliicc iinntteerrffaaccee UUsseerrDDeettaaiillss eexxtteennddss SSeerriiaalliizzaabbllee { Collection<? extends GrantedAuthority> getAuthorities(); String ggeettPPaasssswwoorrdd(); String ggeettUUsseerrnnaammee(); bboooolleeaann iissAAccccoouunnttNNoonnEExxppiirreedd(); bboooolleeaann iissAAccccoouunnttNNoonnLLoocckkeedd(); bboooolleeaann iissCCrreeddeennttiiaallssNNoonnEExxppiirreedd(); bboooolleeaann iissEEnnaabblleedd(); }顾名思义,UserDetails 表⽰详细的⽤⼾信息,这个接⼝涵盖了⼀些必要的⽤⼾信息字段,具体的实现类对它进⾏了扩 展。前⾯我们也介绍了⼀个 Authentication 接⼝,它与 UserDetails 接⼝的定义如下:
虽然 Authentication 与 UserDetails 很类似,但它们之间是有区别的。AAuutthheennttiiccaattiioonn 的的 ggeettCCrreeddeennttiiaallss(()) 与与 UUsseerrDDeettaaiillss 中中的的 ggeettPPaasssswwoorrdd(()) 需需要要被被区区分分对对待待,,前前者者是是⽤⽤⼾⼾提提交交的的密密码码凭凭证证,,后后者者是是⽤⽤⼾⼾正正确确的的密密码码,,认认证证器器其其实实就就 是是对对这这两两者者进进⾏⾏⽐⽐对对。。 此外 Authentication 中的 getAuthorities() 实际是由 UserDetails 的 getAuthorities() 传递⽽形成的还。记得 Authentication 接⼝中的 getUserDetails() ⽅法吗?其中的 UserDetails ⽤⼾详细信息就是经过了 provider (AuthenticationProvider)认证之后被填充的。 44…33…22 UUsseerrDDeettaaiillssSSeerrvviiccee 接接⼝⼝ ⼤多数⾝份验证提供程序都利⽤了 UserDetails 和 UserDetailsService 接⼝。UserDetailsService 接⼝的定义如下: ppuubblliicc iinntteerrffaaccee UUsseerrDDeettaaiillssSSeerrvviiccee { UserDetails llooaaddUUsseerrBByyUUsseerrnnaammee(String username) tthhrroowwss UsernameNotFoundException; }在 UserDetailsService 接⼝中,只有⼀个 loadUserByUsername ⽅法,⽤于通过 username 来加载匹配的⽤⼾。当找 不到 username 对应⽤⼾时,会抛出 UsernameNotFoundException 异常。UUsseerrDDeettaaiillssSSeerrvviiccee 和和 AAuutthheennttiiccaattiioonnPPrroovviiddeerr 两两者者的的职职责责常常常常被被⼈⼈们们搞搞混混,,记记住住⼀⼀点点即即可可,,UUsseerrDDeettaaiillssSSeerrvviiccee 只只负负责责从从特特定定的的地地⽅⽅((通通 常常是是数数据据库库))加加载载⽤⽤⼾⼾信信息息,,仅仅此此⽽⽽已已。。 UserDetailsService 常⻅的实现类有 JdbcDaoImpl,InMemoryUserDetailsManager,前者从数据库加载⽤⼾,后者 从内存中加载⽤⼾,当然你也可以⾃⼰实现 UserDetailsService。 44…44 SSpprriinngg SSeeccuurriittyy AArrcchhiitteeccttuurree 前⾯我们已经介绍了 Spring Security 的核⼼组件(SecurityContextHolder,SecurityContext 和 Authentication) 和核⼼服务(AuthenticationManager,ProviderManager 和 AuthenticationProvider),最后我们再来回顾⼀下 Spring Security 整体架构:
五五、、参参考考资资源源 Docs4dev - Spring Security 中⽂⽂档 Slideshare - spring-security-5 徐靖峰 - Spring Security(⼀)–Architecture Overview 如果看到这⾥,说明你喜欢这篇⽂章,请转转发发、、点点赞赞。微信搜索「web_resource」,关注后回复「进群」或者扫描下⽅⼆维码 即可进⼊⽆⼴告交流群。 ↓扫扫描描⼆⼆维维码码进进群群↓↓
推荐阅读 11… 前后端分离开发,RESTful 接⼝如何设计 22… ⾯试官:讲⼀下 Mybatis 初始化原理 33… 我们再来聊⼀聊 Java 的单例吧 44… 我采访了⼀位 Pornhub ⼯程师 55… 团队开发中 Git 最佳实践 喜欢⽂章,点个在在看看
阅读原⽂ 声明:pdf仅供学习使⽤,⼀切版权归原创公众号所有;建议持续关注原创公众号获取最新⽂章,学习愉快!
Zookeeper:分布式架构详解、分布式技术详解、分布式事务 ⾼级互联⽹架构 Java后端 2019-10-26 点击上⽅ Java后端,选择 设为星标 优质⽂章,及时送达 作者 | Java⾼级互联⽹架构 链接 | toutiao.com/a6742369092881089028/ ⼀⼀、、分分布布式式架架构构详详解解 11、、分分布布式式发发展展历历程程 1.1 单点集中式 特点:App、DB、FileServer都部署在⼀台机器上。并且访问请求量较少 1.2 应⽤服务和数据服务拆分 特点:App、DB、FileServer分别部署在独⽴服务器上。并且访问请求量较少 1.3 使⽤缓存改善性能
特点:数据库中频繁访问的数据存储在缓存服务器中,减少数据库的访问次数,降低数据库的压⼒ 1.4 应⽤服务器集群 特点:多台应⽤服务器通过负载均衡同时对外提供服务,解决单台服务器处理能⼒上限的问题 1.5 数据库读写分离 特点:数据库进⾏读写分离(主从)设计,解决数据库的处理压⼒
1.6 反向代理和CDN加速 特点:采⽤反向代理和CDN加快系统的访问速度 1.7 分布式⽂件系统和分布式数据库 特点:数据库采⽤分布式数据库,⽂件系统采⽤分布式⽂件系统 随着业务的发展,最终数据库读写分离也将⽆法满⾜需求,需要采⽤分布式数据库和分布式⽂件系统来⽀撑 分布式数据库是数据库拆分后的最后⽅法,只有在单表规模⾮常庞⼤的时候才使⽤,更常⽤的数据库拆分⼿段是业务分库,将不 同业务的数据库部署在不同的机器上
⼆⼆、、 分分布布式式技技术术详详解解 11… 并并发发性性 22… 分分布布性性 ⼤任务拆分成多个任务部署到多台机器上对外提供服务 33… 缺缺乏乏全全局局时时钟钟 时间要统⼀ 44… 对对等等性性 ⼀个服务部署在多台机器上是⼀样的,⽆任何差别 55… 故故障障肯肯定定会会发发⽣⽣ 硬盘坏了 CPU烧了… 三三、、分分布布式式事事务务 11… AACCIIDD 原⼦性(Atomicity):⼀个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。 事务在执⾏过程中发⽣错误,会被恢复(Rollback)到事务开始前的状态,就像这个事务从来没有执⾏过⼀样。 ⼀致性(Consistency):在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表⽰写⼊的资料必须完全符合所有 的预设规则,这包含资料的精确度、串联性以及后续数据库可以⾃发性地完成预定的⼯作。 ⽐如A有500元,B有300元,A向B转账100,⽆论怎么样,A和B的总和总是800元 隔离性(Isolation):数据库允许多个并发事务同时对其数据进⾏读写和修改的能⼒,隔离性可以防⽌多个事务并发执⾏时由于 交叉执⾏⽽导致数据的不⼀致。事务隔离分为不同级别,包括读未提交(Read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串⾏化(Serializable)。
持久性(Durability):事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。 22… 22PP//33PP 2P= Two Phase commit ⼆段提交(RDBMS(关系型数据库管理系统)经常就是这种机制,保证强⼀致性) 3P= Three Phase commit 三段提交 说明:2P/3P是为了保证事务的ACID(原⼦性、⼀致性、隔离性、持久性) 2.1 2P的两个阶段 阶段1:提交事务请求(投票阶段)询问是否可以提交事务 阶段2:执⾏事务提交(commit、rollback) 真正的提交事务 2.2 3P的三个阶段 阶段1:是否提交-询问是否可以做事务提交 阶段2:预先提交-预先提交事务 阶段3:执⾏事务提交(commit、rollback)真正的提交事务
说明:3P把2P的阶段⼀拆分成了前⾯两个阶段 33… CCAAPP理理论论 ⼀致性(Consistency):分布式数据库的数据保持⼀致 可⽤性(Availability):任何⼀个节点挂了,其他节点可以继续对外提供服务 分区容错性(⽹络分区)Partition tolerance:⼀个数据库所在的机器坏了,如硬盘坏了,数据丢失了,可以新增⼀台机器,然 后从其他正常的机器把备份的数据同步过来 CAP理论的特点:CAP只能满⾜其中2条 CA(放弃P):将所有的数据放在⼀个节点。满⾜⼀致性、可⽤性。 AP(放弃C):放弃强⼀致性,⽤最终⼀致性来保证。 CP(放弃A):⼀旦系统遇⻅故障,受到影响的服务器需要等待⼀段时间,在恢复期间⽆法对外提供服务。
举例说明CAP理论: 有3台机器分别有3个数据库分别有两张表,数据都是⼀样的 Machine1-db1-tbl_person、tbl_order Machine2-db2-tbl_person、tbl_order Machine3-db3-tbl_person、tbl_order 1)当向machine1的db1的表tbl_person、tbl_order插⼊数数据时,同时要把插⼊的数据同步到machine2、machine3,这就 是⼀致性 2)当其中的⼀台机器宕机了,可以继续对外提供服务,把宕机的机器重新启动起来可以继续服务,这就是可⽤性 3)当machine1的机器坏了,数据全部丢失了,不会有任何问题,因为machine2和machine3上还有数据,重新加⼀台机器 machine4,把machine2和machine3其中⼀台机器的备份数据同步过来就可以了,这就是分区容错性 44… BBAASSEE理理论论 基本可⽤(bascially available)、软状态(soft state)、最终⼀致性(Eventually consistent) 基本可⽤:在分布式系统出现故障,允许损失部分可⽤性(服务降级、⻚⾯降级) 软状态:允许分布式系统出现中间状态。⽽且中间状态不影响系统的可⽤性。 1、这⾥的中间状态是指不同的data replication之间的数据更新可以出现延时的最终⼀致性 2、如CAP理论⾥⾯的⽰例,当向machine1的db1的表tbl_person、tbl_order插⼊数数据时,同时要把插⼊的数据同步到 machine2、machine3,当machine3的⽹络有问题时,同步失败,但是过⼀会⽹络恢复了就同步成功了,这个同步失败的状态 就称为软状态,因为最终还是同步成功了。 最终⼀致性:data replications经过⼀段时间达到⼀致性。 55… PPaaxxooss算算法法 5.1 介绍Paxos算法之前我们先来看⼀个⼩故事 拜占庭将军问题 拜占庭帝国就是5~15世纪的东罗⻢帝国,拜占庭即现在⼟⽿其的伊斯坦布尔。我们可以想象,拜占庭军队有许多分⽀,驻扎在敌 ⼈城外,每⼀分⽀由各⾃的将军指挥。假设有11位将军,将军们只能靠通讯员进⾏通讯。在观察敌⼈以后,忠诚的将军们必须制 订⼀个统⼀的⾏动计划——进攻或者撤退。然⽽,这些将军⾥有叛徒,他们不希望忠诚的将军们能达成⼀致,因⽽影响统⼀⾏动 计划的制订与传播。 问题是:将军们必须有⼀个协议,使所有忠诚的将军们能够达成⼀致,⽽且少数⼏个叛徒不能使忠诚的将军们作出错误的计划 ——使有些将军进攻⽽另⼀些将军撤退。 假设有9位忠诚的将军,5位判断进攻,4位判断撤退,还有2个间谍恶意判断撤退,虽然结果是错误的撤退,但这种情况完全是允 许的。因为这11位将军依然保持着状态⼀致性。
总总结结:: 1)11位将军进攻城池 2)同时进攻(议案、决议)、同时撤退(议案、决议) 3)不管撤退还是进攻,必须半数的将军统⼀意⻅才可以执⾏ 4)将军⾥⾯有叛徒,会⼲扰决议⽣成 5.2 下⾯就来介绍⼀下Paxos算法 Google Chubby的作者Mike Burrows说过这个世界上只有⼀种⼀致性算法,那就是Paxos,其它的算法都是残次品。 Paxos:多数派决议(最终解决⼀致性问题) Paxos算法有三种⻆⾊:Proposer,Acceptor,Learner Proposer:提交者(议案提交者) 提交议案(判断是否过半),提交批准议案(判断是否过半) Acceptor:接收者(议案接收者) 接受议案或者驳回议案,给proposer回应(promise) Learner:学习者(打酱油的) 如果议案产⽣,学习议案。 设定1:如果Acceptor没有接受议案,那么他必须接受第⼀个议案
设定2:每个议案必须有⼀个编号,并且编号只能增⻓,不能重复。越往后越⼤。 设定3:接受编号⼤的议案,如果⼩于之前接受议案编号,那么不接受 设定4:议案有2种(提交的议案,批准的议案) 1)Prepare阶段(议案提交) a)Proposer希望议案V。⾸先发出Prepare请求⾄⼤多数Acceptor。Prepare请求内容为序列号K b)Acceptor收到Prepare请求为编号K后,检查⾃⼰⼿⾥是否有处理过Prepare请求。 c)如果Acceptor没有接受过任何Prepare请求,那么⽤OK来回复Proposer,代表Acceptor必须接受收到的第⼀个议案(设定 1)d)否则,如果Acceptor之前接受过任何Prepare请求(如:MaxN),那么⽐较议案编号,如果K<MaxN,则⽤reject或者error 回复Proposer e)如果K>=MaxN,那么检查之前是否有批准的议案,如果没有则⽤OK来回复Proposer,并记录K f)如果K>=MaxN,那么检查之前是否有批准的议案,如果有则回复批准的议案编号和议案内容(如:<AcceptN, AcceptV>, AcceptN为批准的议案编号,AcceptV为批准的议案内容) 2)Accept阶段(批准阶段) a)Proposer收到过半Acceptor发来的回复,回复都是OK,且没有附带任何批准过的议案编号和议案内容。那么Proposer继续 提交批准请求,不过此时会连议案编号K和议案内容V⼀起提交(<K, V>这种数据形式) b)Proposer收到过半Acceptor发来的回复,回复都是OK,且附带批准过的议案编号和议案内容(<pok,议案编号,议案内容 >)。那么Proposer找到所有回复中超过半数的那个(假设为<pok,AcceptNx,AcceptVx>)作为提交批准请求(请求为
<K,AcceptVx>)发送给Acceptor。 c)Proposer没有收到过半Acceptor发来的回复,则修改议案编号K为K+1,并将编号重新发送给Acceptors(重复Prepare阶段 的过程) d)Acceptor收到Proposer发来的Accept请求,如果编号K<MaxN则不回应或者reject。 e)Acceptor收到Proposer发来的Accept请求,如果编号K>=MaxN则批准该议案,并设置⼿⾥批准的议案为<K,接受议案的编 号,接受议案的内容>,回复Proposer。 f)经过⼀段时间Proposer对⽐⼿⾥收到的Accept回复,如果超过半数,则结束流程(代表议案被批准),同时通知Leaner可以 学习议案。 g) 经过⼀段时间Proposer对⽐⼿⾥收到的Accept回复,如果未超过半数,则修改议案编号重新进⼊Prepare阶段。 tips: 欢迎关注微信公众号:Java后端,获取更多推送。 5.3 Paxos⽰例 ⽰例1:先后提议的场景 ⻆⾊: proposer:参谋1,参谋2 acceptor:将军1,将军2,将军3(决策者) 1)参谋1发起提议,派通信兵带信给3个将军,内容为(编号1); 2)3个将军收到参谋1的提议,由于之前还没有保存任何编号,因此把(编号1)保存下来,避免遗忘;同时让通信兵带信回去, 内容为(ok); 3)参谋1收到⾄少2个将军的回复,再次派通信兵带信给3个将军,内容为(编号1,进攻时间1); 4)3个将军收到参谋1的时间,把(编号1,进攻时间1)保存下来,避免遗忘;同时让通信兵带信回去,内容为(Accepted); 5)参谋1收到⾄少2个将军的(Accepted)内容,确认进攻时间已经被⼤家接收; 6)参谋2发起提议,派通信兵带信给3个将军,内容为(编号2); 7)3个将军收到参谋2的提议,由于(编号2)⽐(编号1)⼤,因此把(编号2)保存下来,避免遗忘;⼜由于之前已经接受参谋 1的提议,因此让通信兵带信回去,内容为(编号1,进攻时间1); 8)参谋2收到⾄少2个将军的回复,由于回复中带来了已接受的参谋1的提议内容,参谋2因此不再提出新的进攻时间,接受参谋 1提出的时间; ⽰例2:交叉场景
⻆⾊: proposer:参谋1,参谋2 acceptor:将军1,将军2,将军3(决策者) 1)参谋1发起提议,派通信兵带信给3个将军,内容为(编号1); 2)3个将军的情况如下 a)将军1和将军2收到参谋1的提议,将军1和将军2把(编号1)记录下来,如果有其他参谋提出更⼩的编号,将被拒绝;同时让 通信兵带信回去,内容为(ok); b)负责通知将军3的通信兵被抓,因此将军3没收到参谋1的提议; 3)参谋2在同⼀时间也发起了提议,派通信兵带信给3个将军,内容为(编号2); 4)3个将军的情况如下 a)将军2和将军3收到参谋2的提议,将军2和将军3把(编号2)记录下来,如果有其他参谋提出更⼩的编号,将被拒绝;同时让 通信兵带信回去,内容为(ok); b)负责通知将军1的通信兵被抓,因此将军1没收到参谋2的提议; 5)参谋1收到⾄少2个将军的回复,再次派通信兵带信给有答复的2个将军,内容为(编号1,进攻时间1); 6)2个将军的情况如下 a)将军1收到了(编号1,进攻时间1),和⾃⼰保存的编号相同,因此把(编号1,进攻时间1)保存下来;同时让通信兵带信 回去,内容为(Accepted); b)将军2收到了(编号1,进攻时间1),由于(编号1)⼩于已经保存的(编号2),因此让通信兵带信回去,内容为 (Rejected,编号2); 7)参谋2收到⾄少2个将军的回复,再次派通信兵带信给有答复的2个将军,内容为(编号2,进攻时间2); 8)将军2和将军3收到了(编号2,进攻时间2),和⾃⼰保存的编号相同,因此把(编号2,进攻时间2)保存下来,同时让通信 兵带信回去,内容为(Accepted); 9)参谋2收到⾄少2个将军的(Accepted)内容,确认进攻时间已经被多数派接受; 10)参谋1只收到了1个将军的(Accepted)内容,同时收到⼀个(Rejected,编号2);参谋1重新发起提议,派通信兵带信给 3个将军,内容为(编号3);
11)3个将军的情况如下 a)将军1收到参谋1的提议,由于(编号3)⼤于之前保存的(编号1),因此把(编号3)保存下来;由于将军1已经接受参谋1 前⼀次的提议,因此让通信兵带信回去,内容为(编号1,进攻时间1); b)将军2收到参谋1的提议,由于(编号3)⼤于之前保存的(编号2),因此把(编号3)保存下来;由于将军2已经接受参谋2 的提议,因此让通信兵带信回去,内容为(编号2,进攻时间2); c)负责通知将军3的通信兵被抓,因此将军3没收到参谋1的提议; 12)参谋1收到了⾄少2个将军的回复,⽐较两个回复的编号⼤⼩,选择⼤编号对应的进攻时间作为最新的提议;参谋1再次派通 信兵带信给有答复的2个将军,内容为(编号3,进攻时间2); 13)将军1和将军2收到了(编号3,进攻时间2),和⾃⼰保存的编号相同,因此保存(编号3,进攻时间2),同时让通信兵带 信回去,内容为(Accepted); 14)参谋1收到了⾄少2个将军的(accepted)内容,确认进攻时间已经被多数派接受。 四. Zookeeper ZAB协议 Zookeeper Automic Broadcast(ZAB),即Zookeeper原⼦性⼴播,是Paxos经典实现 术语: quorum:集群过半数的集合 1. ZAB(zookeeper)中节点分四种状态 looking:选举Leader的状态(崩溃恢复状态下) following:跟随者(follower)的状态,服从Leader命令 leading:当前节点是Leader,负责协调⼯作。 observing:observer(观察者),不参与选举,只读节点。 2. ZAB中的两个模式(ZK是如何进⾏选举的) 崩溃恢复、消息⼴播 1)崩溃恢复 leader挂了,需要选举新的leader
a.每个server都有⼀张选票,如(3,9),选票投⾃⼰。 b.每个server投完⾃⼰后,再分别投给其他还可⽤的服务器。如把Server3的(3,9)分别投给Server4和Server5,⼀次类推 c.⽐较投票,⽐较逻辑:优先⽐较Zxid,Zxid相同时才⽐较myid。⽐较Zxid时,⼤的做leader;⽐较myid时,⼩的做leader d.改变服务器状态(崩溃恢复->数据同步,或者崩溃恢复->消息⼴播) 相关概念补充说明: epoch周期值 acceptedEpoch(⽐喻:年号):follower已经接受leader更改年号的(newepoch)提议。 currentEpoch(⽐喻:当前的年号):当前的年号 lastZxid:history中最近接收到的提议zxid(最⼤的值) history:当前节点接受到事务提议的log Zxid数据结构说明: cZxid = 0x10000001b 64位的数据结构 ⾼32位:10000 Leader的周期编号+myid的组合 低32位:001b 事务的⾃增序列(单调递增的序列)只要客⼾端有请求,就+1 当产⽣新Leader的时候,就从这个Leader服务器上取出本地log中最⼤事务Zxid,从⾥⾯读出epoch+1,作为⼀个新epoch,并 将低32位置0(保证id绝对⾃增) 2)消息⼴播(类似2P提交)
a.Leader接受请求后,将这个请求赋予全局的唯⼀64位⾃增Id(zxid)。 b.将zxid作为议案发给所有follower。 c.所有的follower接受到议案后,想将议案写⼊硬盘后,⻢上回复Leader⼀个ACK(OK)。 d.当Leader接受到合法数量(过半)Acks,Leader给所有follower发送commit命令。 e.follower执⾏commit命令。 注意:到了这个阶段,ZK集群才正式对外提供服务,并且Leader可以进⾏消息⼴播,如果有新节点加⼊,还需要进⾏同步。 3)数据同步 a.取出Leader最⼤lastZxid(从本地log⽇志来) b.找到对应zxid的数据,进⾏同步(数据同步过程保证所有follower⼀致) c.只有满⾜quorum同步完成,准Leader才能成为真正的Leader -E N D- 如果看到这⾥,说明你喜欢这篇⽂章,请转转发发、、点点赞赞。微信搜索「web_resource」,关注后回复「进群」或者扫描下⽅⼆维码 即可进⼊⽆⼴告交流群。 ↓扫扫描描⼆⼆维维码码进进群群↓↓
阅读原⽂ 声明:pdf仅供学习使⽤,⼀切版权归原创公众号所有;建议持续关注原创公众号获取最新⽂章,学习愉快! 推荐阅读 11… Java 开发中如何正确的踩坑 22… 当我遵循了这 16 条规范写代码 33… 理解 IntelliJ IDEA 的项⽬配置和 Web 部署 44… Java 开发中常⽤的 4 种加密⽅法 55… 团队开发中 Git 最佳实践 学Java,请关注公众号:Java后端 喜欢⽂章,点个在在看看
【架构】互联⽹公司的技术架构 VectorJin Java后端 2⽉28⽇ 本⽂探讨了互联⽹公司的技术架构,涉及DNS、负载均衡、⻓连接、API⽹关、PUSH推送、微服务、分布式事务以及相关⽀撑的 基础服务。主要是为了学习,希望可以给⼤家⼀个参考。 整整体体架架构构 APP、PC以及第三⽅等调⽤⽅通过传统的域名解析服务LocalDNS获取负载均衡器的IP,APP可以通过HttpDNS的⽅式来实现更 实时和灵活精准的域名解析服务。 通过负载均衡器到达统⼀接⼊层,统⼀接⼊层维护⻓连接 。 API⽹关作为微服务的⼊⼝,负责协议转换、请求路由、认证鉴权、流量控制、数据缓存等。 业务Server通过PUSH推送系统来实现对端的实时推送,如IM、通知等功能。 业务Server之间通过专有的RPC协议实现相互调⽤,并通过NAT⽹关调⽤外部第三⽅服务。 域域名名解解析析 传统DNS
DNS(Domain Name System)域名系统,⼀种分布式⽹络⽬录服务,⽤于域名与IP地址的相互转换,能够使⼈更⽅便的访问互 联⽹,⽽不⽤去记住机器的IP地址。 DNS的解析过程如下: 客⼾端递归查询LocalDNS(⼀般是ISP互联⽹服务提供商提供的边缘DNS服务器)获取IP LocalDNS迭代查询获取IP,即不断的获取域名服务器的地址进⾏查询 HHttttppDDNNSS 移动解析(HttpDNS)基于Http协议向DNS服务器发送域名解析请求,替代了基于DNS协议向运营商Local DNS发起解析请求的 传统⽅式,可以避免Local DNS造成的域名劫持和跨⽹访问问题,解决移动互联⽹服务中域名解析异常带来的困扰。 以腾讯云HttpDNS为参考,相较于传统LocalDNS的优势对⽐:
负负载载均均衡衡 为了解决单台机器的性能问题以及单点问题,需要通过负负载载均均衡衡将多台机器进⾏⽔平扩展,将请求流量分发到不同的服务器上 ⾯。客⼾端的流量⾸先会到达负载均衡服务器,由负载均衡服务器通过⼀定的调度算法将流量分发到不同的应⽤服务器上⾯,同时负 载均衡服务器也会对应⽤服务器做周期性的健康检查,当发现故障节点时便动态的将节点从应⽤服务器集群中剔除,以此来保证 应⽤的⾼可⽤。 ⽹络负载均衡主要有硬件与软件两种实现⽅式,主流负载均衡解决⽅案中,硬件⼚商以F5为代表,软件主要为LVS、NGINX、 HAProxy。 技术原理上分为L4四层负载均衡和L7七层负载均衡。 LL44 vvss LL77 L4四层负载均衡⼯作于处于OSI模型的传输层,主要⼯作是转转发发。它在接收到客⼾端报⽂后,需要了解传输层的协议内容,根据 预设的转发模式和调度算法将报⽂转发到应⽤服务器。以TCP为例,当⼀个TCP连接的初始SYN报⽂到达时,调度器就选择⼀台 服务器,将报⽂转发给它。此后通过查发报⽂的IP和TCP报⽂头地址,保证此连接的后继报⽂被转发到该服务器。
L7七层负载均衡⼯作在OSI模型的应⽤层,主要⼯作就是代代理理。七层负载均衡会与客⼾端建⽴⼀条完整的连接并将应⽤层的请求 解析出来,再按照调度算法选择⼀个应⽤服务器,并与应⽤服务器建⽴另外⼀条连接将请求发送过去。 LLVVSS转转发发模模式式 LVS(IP负载均衡技术)⼯作在L4四层以下,转发模式有:DR模式、NAT模式、TUNNEL模式、FULL NAT模式。 DDRR模模式式(直接路由) 改写请求报⽂的MAC地址,将请求发送到真实服务器,⽽真实服务器将响应直接返回给客⼾。要求调度器与真实服务器都有⼀块 ⽹卡连在同⼀物理⽹段上,并且真实服务器需要配置VIP。 NNAATT模模式式 (⽹络地址转换)
调度器重写请求报⽂的⽬标地址,根据预设的调度算法,将请求分派给后端的真实服务器;真实服务器的响应报⽂通过调度器 时,报⽂的源地址被重写,再返回给客⼾,完成整个负载调度过程。要求负载均衡需要以⽹关的形式存在于⽹络中。 TTUUNNNNEELL模模式式 调度器把请求报⽂通过IP隧道转发⾄真实服务器,⽽真实服务器将响应直接返回给客⼾,所以调度器只处理请求报⽂。要求真实 服务器⽀持隧道协议和配置VIP。 FFUULLLL NNAATT模模式式
在NAT模式的基础上做⼀次源地址转换(即SNAT),做SNAT的好处是可以让应答流量经过正常的三层路由回到负载均衡上,这 样负载均衡就不需要以⽹关的形式存在于⽹络中了。性能要逊⾊于NAT模式,真实服务器会丢失客⼾端的真实IP地址。 调调度度算算法法 轮轮询询将外部请求按顺序轮流分配到集群中的真实服务器上,它均等地对待每⼀台服务器,⽽不管服务器上实际的连接数和系统负载。 加加权权轮轮询询 权值越⼤分配到的访问概率越⾼,主要⽤于后端每台服务器性能不均衡的情况下,达到合理有效的地利⽤主机资源。 最最少少连连接接 将⽹络请求调度到已建⽴的链接数最少的服务器上。如果集群系统的真实服务器具有相近的系统性能,采⽤"最⼩连接"调度算法 可以较好地均衡负载 哈哈希希将指定的Key的哈希值与服务器数⽬进⾏取模运算,获取要求的服务器的序号 ⼀⼀致致性性哈哈希希 考虑到分布式系统每个节点都有可能失效,并且新的节点很可能动态的增加进来,⼀致性哈希可以保证当系统的节点数⽬发⽣变 化时尽可能减少访问节点的移动。 AAPPII⽹⽹关关 API⽹关(API Gateway)是⼀个服务器集群,对外的唯⼀⼊⼝。从⾯向对象设计的⻆度看,它与外观模式类似。API⽹关封装了 系统内部架构,对外提供REST/HTTP的访问API。同时还具有其它⾮业务相关的职责,如⾝份验证、监控、负载均衡、缓存、流 量控制等。 AAPPII管管理理
API⽹关核⼼功能是 API 管理。提供 API 的完整⽣命周期管理,包括创建、维护、发布、运⾏、下线等基础功能;提供测试,预 发布,发布等多种环境;提供版本管理,版本回滚。 API配置包括 前前端端配配置置 和后后端端配配置置 。前端配置指的是Http相关的配置,如HTTP ⽅法、URL路径,请求参数等。后端配置指的是 微服务的相关配置,如服务名称、服务参数等。这样通过API配置,就完成了前端Http到后端微服务的转换。 全全异异步步 由于API⽹关主要处理的是⽹络I/O,那么通过⾮阻塞I/O以及I/O多路复⽤,就可以达到使⽤少量线程承载海量并发处理,避免线 程上下⽂切换,⼤⼤增加系统吞吐量,减少机器成本。 常⽤解决⽅案有 Tomcat/Jetty+NIO+servlet3.1 和 NNeettttyy++NNIIOO,这⾥推荐Netty+NIO,能实现更⾼的吞吐量。Spring 5.0 推出 的WebFlux反应式编程模型,特点是异步的、事件驱动的、⾮阻塞,内部就是基于Netty+NIO 或者 SSeerrvvlleett 33…11 NNoonn–BBlloocckkiinngg IIOO容器 实现的。 链链式式处处理理 链式处理即通过责任链模式,基于 Filter 链的⽅式提供了⽹关基本的功能,例如:路由、协议转换、缓存、限流、监控、⽇志。 也可以根据实际的业务需要进⾏扩展,但注意不要做耗时操作。 Spring cloud gateway (基于 Spring WebFlux)的⼯作机制⼤体如下: 1. Gateway 接收客⼾端请求。 2. 客⼾端请求与路由信息进⾏匹配,匹配成功的才能够被发往相应的下游服务。
3. 请求经过 Filter 过滤器链,执⾏ pre 处理逻辑,如修改请求头信息等。 4. 请求被转发⾄下游服务并返回响应。 5. 响应经过 Filter 过滤器链,执⾏ post 处理逻辑。 6. 向客⼾端响应应答。 请请求求限限流流 请求限流是在⾯对未知流量的情况下,防⽌系统被冲垮的最后⼀道有效的防线。可以针对集群、业务系统和具体API维度进⾏限 流。具体实现可以分为集群版和单机版,区别就是集群版是使⽤后端统⼀缓存如Redis存储数据,但有⼀定的性能损耗;单单机机版版则在 本机内存中进⾏存储(推荐)。 常⽤的限流算法:计数器、漏桶、令令牌牌桶桶(推荐) 熔熔断断降降级级 服服务务熔熔断断 当下游的服务因为某种原因突然变得不可⽤或响应过慢,上游服务为了保证⾃⼰整体服务的可⽤性,不再继续调⽤⽬标服务,直 接返回,快速释放资源。如果⽬标服务情况好转则恢复调⽤。 熔断是为了解决服务雪崩,特别是在微服务体系下,通常在框架层⾯进⾏处理。内部机制采⽤的是断路器模式,其内部状态转换 图如下: 服服务务降降级级 当负荷超出系统整体负载承受能⼒时,为了保证核⼼服务的可⽤,通常可以对⾮核⼼服务进⾏降级,如果返回缓存内容或者直接 返回。 服务降级的粒度可以是API维度、功能维度、甚⾄是系统维度,但是都需要事前进⾏服务级别的梳理和定义。 真实场景下,通常是在服务器负载超出阈值报警之后,管理员决定是扩容还是降级。 业业务务隔隔离离
API⽹关统⼀了⾮业务层⾯的处理,但如果有业务处理的逻辑,不同业务之间就可能会相互影响。要进⾏业务系统的隔离,通常 可以采⽤线程池隔离和集群隔离,但对于Java⽽⾔,线程是⽐较重的资源,推荐使⽤集群隔离。 PPUUSSHH推推送送 消息推送系统 针对不同的场景推出多种推送类型,满⾜⽤⼾的个性化推送需求,并集成了苹果、华为、⼩⽶、FCM 等⼚商渠道 的推送功能,在提供控制台快速推送能⼒的同时,也提供了服务端接⼊⽅案,⽅便⽤⼾快速集成移动终端推送功能,与⽤⼾保 持互动,从⽽有效地提⾼⽤⼾留存率,提升⽤⼾体验。 设设备备建建连连、、注注册册、、绑绑定定⽤⽤⼾⼾流流程程 消消息息推推送送过过程程
在⾮常多的业务场景中,当业务发⽣时⽤⼾未必在线,也未必有⽹络。因此,在 MPS 中所有消息均会被持久化。 业务发⽣时,MPS 会尝试做⼀次推送(第三⽅渠道推送或⾃建的TCP 连接推送)。⾃建渠道中,会通过查询缓存来判断⽤⼾的终端是否有 TCP 连接, 如果存在则推送,客⼾端收到推送消息后,会给服务端回执,服务端即可更新消息状态。如果推送失败,或者回执丢失,⽤⼾在下 ⼀次建⽴连接时,会重新接受消息通知,同时客⼾端会进⾏逻辑去重。 微微服服务务体体系系 参参考考资资料料 www.linuxvirtualserver.org/zh/lvs1.htm… www.infoq.cn/article/Mag… www.cnblogs.com/mindwind/p/… blog.csdn.net/gaopeiliang… www.jianshu.com/p/76cc8ba5c… www.jianshu.com/p/cda7c0366… juejin.im/post/5c63ab…
阅读原⽂ 声明:pdf仅供学习使⽤,⼀切版权归原创公众号所有;建议持续关注原创公众号获取最新⽂章,学习愉快! -E N D- 如果看到这⾥,说明你喜欢这篇⽂章,请转转发发、、点点赞赞。微信搜索「web_resource」,欢迎添加⼩编微信「focusoncode」,每 ⽇朋友圈更新⼀篇⾼质量技术博⽂(⽆⼴告)。 ↓扫扫描描⼆⼆维维码码添添加加⼩⼩编编↓↓ 推荐阅读 11… 浅谈 Web ⽹站架构演变过程 22… ⼀个⾮常实⽤的特性,很多⼈没⽤过 33… Spring Boot 2.x 操作缓存的新姿势 44… MyBatis 中 SQL 写法技巧⼩总结
【架构】浅谈 Web ⽹站架构演变过程 ⼩M的博客 Java后端 2⽉27⽇ 我们以javaweb为例,来搭建⼀个简单的电商系统,看看这个系统可以如何⼀步步演变。 该系统具备的功能: ⽤⼾模块:⽤⼾注册和管理 商品模块:商品展⽰和管理 交易模块:创建交易和管理 ⽹站的初期,我们经常会在单机上跑我们所有的程序和软件。此时我们使⽤⼀个容器,如tomcat、jetty、jboos,然后直接使⽤ JSP/servlet技术,或者使⽤⼀些开源的框架如maven+spring+struct+hibernate、maven+spring+springmvc+mybatis;最 后再选择⼀个数据库管理系统来存储数据,如mysql、sqlserver、oracle,然后通过JDBC进⾏数据库的连接和操作。 把以上的所有软件都装载同⼀台机器上,应⽤跑起来了,也算是⼀个⼩系统了。此时系统结果如下: 随着⽹站的上线,访问量逐步上升,服务器的负载慢慢提⾼,在服务器还没有超载的时候,我们应该就要做好准备,提升⽹站的 负载能⼒。假如我们代码层⾯已难以优化,在不提⾼单台机器的性能的情况下,增加机器是⼀个不错的⽅式,不仅可以有效地提 ⾼系统的负载能⼒,⽽且性价⽐⾼。 增加的机器⽤来做什么呢?此时我们可以把数据库,web服务器拆分开来,这样不仅提⾼了单台机器的负载能⼒,也提⾼了容灾 能⼒。 应⽤服务器与数据库分开后的架构如下图所⽰:
随着访问量继续增加,单台应⽤服务器已经⽆法满⾜需求了。在假设数据库服务器没有压⼒的情况下,我们可以把应⽤服务器从 ⼀台变成了两台甚⾄多台,把⽤⼾的请求分散到不同的服务器中,从⽽提⾼负载能⼒。多台应⽤服务器之间没有直接的交互,他 们都是依赖数据库各⾃对外提供服务。著名的做故障切换的软件有keepalived,keepalived是⼀个类似于layer3、4、7交换机 制的软件,他不是某个具体软件故障切换的专属品,⽽是可以适⽤于各种软件的⼀款产品。keepalived配合上ipvsadm⼜可以做 负载均衡,可谓是神器。 往期关于架构⽂章可以关注微信公众号:Java后端,后台回复 技术博⽂ 获取。 我们以增加了⼀台应⽤服务器为例,增加后的系统结构图如下: 系系统统演演变变到到这这⾥⾥,,将将会会出出现现下下⾯⾯四四个个问问题题: 1. ⽤⼾的请求由谁来转发到到具体的应⽤服务器 2. 有什么转发的算法 3. 应⽤服务器如何返回⽤⼾的请求 4. ⽤⼾如果每次访问到的服务器不⼀样,那么如何维护session的⼀致性 我我们们来来看看看看解解决决问问题题的的⽅⽅案案: 第第⼀⼀个个问问题题即即是是负负载载均均衡衡的的问问题题,,⼀⼀般般有有55种种解解决决⽅⽅案案:: 1、hhttttpp重重定定向向。HTTP重定向就是应⽤层的请求转发。⽤⼾的请求其实已经到了HTTP重定向负载均衡服务器,服务器根据算法 要求⽤⼾重定向,⽤⼾收到重定向请求后,再次请求真正的集群 优点:简单。 缺点:性能较差。
2、DDNNSS域域名名解解析析负负载载均均衡衡。DNS域名解析负载均衡就是在⽤⼾请求DNS服务器,获取域名对应的IP地址时,DNS服务器直接给 出负载均衡后的服务器IP。 优点:交给DNS,不⽤我们去维护负载均衡服务器。 缺点:当⼀个应⽤服务器挂了,不能及时通知DNS,⽽且DNS负载均衡的控制权在域名服务商那⾥,⽹站⽆法做更多的改善和更 强⼤的管理。 3、反反向向代代理理服服务务器器。在⽤⼾的请求到达反向代理服务器时(已经到达⽹站机房),由反向代理服务器根据算法转发到具体的服 务器。常⽤的apache,nginx都可以充当反向代理服务器。 优点:部署简单。 缺点:代理服务器可能成为性能的瓶颈,特别是⼀次上传⼤⽂件。 4、IIPP层层负负载载均均衡衡。在请求到达负载均衡器后,负载均衡器通过修改请求的⽬的IP地址,从⽽实现请求的转发,做到负载均衡。 优点:性能更好。 缺点:负载均衡器的宽带成为瓶颈。 5、数数据据链链路路层层负负载载均均衡衡。在请求到达负载均衡器后,负载均衡器通过修改请求的mac地址,从⽽做到负载均衡,与IP负载均衡 不⼀样的是,当请求访问完服务器之后,直接返回客⼾。⽽⽆需再经过负载均衡器。 第第⼆⼆个个问问题题即即是是集集群群调调度度算算法法问问题题,,常常⻅⻅的的调调度度算算法法有有1100种种。。 1、rrrr 轮轮询询调调度度算算法法。顾名思义,轮询分发请求。 优点:实现简单 缺点:不考虑每台服务器的处理能⼒ 2、wwrrrr 加加权权调调度度算算法法。我们给每个服务器设置权值weight,负载均衡调度器根据权值调度服务器,服务器被调⽤的次数跟权值 成正⽐。 优点:考虑了服务器处理能⼒的不同 3、sshh 原原地地址址散散列列:提取⽤⼾IP,根据散列函数得出⼀个key,再根据静态映射表,查处对应的value,即⽬标服务器IP。过⽬标 机器超负荷,则返回空。 4、ddhh ⽬⽬标标地地址址散散列列:同上,只是现在提取的是⽬标地址的IP来做哈希。 优点:以上两种算法的都能实现同⼀个⽤⼾访问同⼀个服务器。 5、llcc 最最少少连连接接。优先把请求转发给连接数少的服务器。 优点:使得集群中各个服务器的负载更加均匀。
6、wwllcc 加加权权最最少少连连接接。在lc的基础上,为每台服务器加上权值。算法为:(活动连接数*256+⾮活动连接数)÷权重 ,计算出来 的值⼩的服务器优先被选择。 优点:可以根据服务器的能⼒分配请求。 7、sseedd 最最短短期期望望延延迟迟。其实sed跟wlc类似,区别是不考虑⾮活动连接数。算法为:(活动连接数+1)256÷权重,同样计算出来 的值⼩的服务器优先被选择。 8、nnqq 永永不不排排队队。改进的sed算法。我们想⼀下什么情况下才能“永不排队”,那就是服务器的连接数为0的时候,那么假如有服 务器连接数为0,均衡器直接把请求转发给它,⽆需经过sed的计算。 9、LLBBLLCC 基基于于局局部部性性的的最最少少连连接接。均衡器根据请求的⽬的IP地址,找出该IP地址最近被使⽤的服务器,把请求转发之,若该服务 器超载,最采⽤最少连接数算法。 10、LLBBLLCCRR 带带复复制制的的基基于于局局部部性性的的最最少少连连接接。均衡器根据请求的⽬的IP地址,找出该IP地址最近使⽤的“服务器组”,注意,并 不是具体某个服务器,然后采⽤最少连接数从该组中挑出具体的某台服务器出来,把请求转发之。若该服务器超载,那么根据最 少连接数算法,在集群的⾮本服务器组的服务器中,找出⼀台服务器出来,加⼊本服务器组,然后把请求转发之。 第第三三个个问问题题是是集集群群模模式式问问题题,,⼀⼀般般33种种解解决决⽅⽅案案:: 1、NNAATT:负载均衡器接收⽤⼾的请求,转发给具体服务器,服务器处理完请求返回给均衡器,均衡器再重新返回给⽤⼾。 2、DDRR:负载均衡器接收⽤⼾的请求,转发给具体服务器,服务器出来玩请求后直接返回给⽤⼾。需要系统⽀持IP Tunneling协 议,难以跨平台。 3、TTUUNN:同上,但⽆需IP Tunneling协议,跨平台性好,⼤部分系统都可以⽀持。 4、第四个问题是session问题,⼀般有4种解决⽅案: SSeessssiioonn SSttiicckkyy。session sticky就是把同⼀个⽤⼾在某⼀个会话中的请求,都分配到固定的某⼀台服务器中,这样我们就不需要 解决跨服务器的session问题了,常⻅的算法有ip_hash法,即上⾯提到的两种散列算法。 优点:实现简单。 缺点:应⽤服务器重启则session消失。 SSeessssiioonn RReepplliiccaattiioonn。session replication就是在集群中复制session,使得每个服务器都保存有全部⽤⼾的session数据。 优点:减轻负载均衡服务器的压⼒,不需要要实现ip_hasp算法来转发请求。 缺点:复制时宽带开销⼤,访问量⼤的话session占⽤内存⼤且浪费。 SSeessssiioonn数数据据集集中中存存储储:session数据集中存储就是利⽤数据库来存储session数据,实现了session和应⽤服务器的解耦。 优点:相⽐session replication的⽅案,集群间对于宽带和内存的压⼒减少了很多。 缺点:需要维护存储session的数据库。
CCooookkiiee BBaassee:cookie base就是把session存在cookie中,有浏览器来告诉应⽤服务器我的session是什么,同样实现了session 和应⽤服务器的解耦。 优点:实现简单,基本免维护。 缺点:cookie⻓度限制,安全性低,宽带消耗。 值值得得⼀⼀提提的的是是: nginx⽬前⽀持的负载均衡算法有wrr、sh(⽀持⼀致性哈希)、fair(本⼈觉得可以归结为lc)。但nginx作为均衡器的话,还可 以⼀同作为静态资源服务器。 keepalived+ipvsadm⽐较强⼤,⽬前⽀持的算法有:rr、wrr、lc、wlc、lblc、sh、dh keepalived⽀持集群模式有:NAT、DR、TUN nginx本⾝并没有提供session同步的解决⽅案,⽽apache则提供了session共享的⽀持。 好了,解决了以上的问题之后,系系统统的的结结构构如如下下: 上⾯我们总是假设数据库负载正常,但随着访问量的的提⾼,数据库的负载也在慢慢增⼤。那么可能有⼈⻢上就想到跟应⽤服务 器⼀样,把数据库⼀份为⼆再负载均衡即可。但对于数据库来说,并没有那么简单。假如我们简单的把数据库⼀分为⼆,然后对 于数据库的请求,分别负载到A机器和B机器,那么显⽽易⻅会造成两台数据库数据不统⼀的问题。那么对于这种情况,我们可以 先考虑使⽤读写分离的⽅式。 读写分离后的数据库系统结构如下:
这这个个结结构构变变化化后后也也会会带带来来两两个个问问题题: 主从数据库之间数据同步问题 应⽤对于数据源的选择问题 解解决决问问题题⽅⽅案案: 我们可以使⽤MYSQL⾃带的master+slave的⽅式实现主从复制。 采⽤第三⽅数据库中间件,例如mycat。mycat是从cobar发展⽽来的,⽽cobar是阿⾥开源的数据库中间件,后来停⽌开 发。mycat是国内⽐较好的mysql开源数据库分库分表中间件。 数据库做读库的话,常常对模糊查找⼒不从⼼,即使做了读写分离,这个问题还未能解决。以我们所举的交易⽹站为例,发布的 商品存储在数据库中,⽤⼾最常使⽤的功能就是查找商品,尤其是根据商品的标题来查找对应的商品。对于这种需求,⼀般我们 都是通过like功能来实现的,但是这种⽅式的代价⾮常⼤。此时我们可以使⽤搜索引擎的倒排索引来完成。 搜搜索索引引擎擎具具有有以以下下优优点点: 引引⼊⼊搜搜索索引引擎擎后后也也会会带带来来以以下下的的开开销销: 带来⼤量的维护⼯作,我们需要⾃⼰实现索引的构建过程,设计全量/增加的构建⽅式来应对⾮实时与实时的查询需求。 需要维护搜索引擎集群 搜索引擎并不能替代数据库,他解决了某些场景下的“读”的问题,是否引⼊搜索引擎,需要综合考虑整个系统的需求。引⼊搜 索引擎后的系统结构如下:
1、后后台台应应⽤⽤层层和和数数据据库库层层的的缓缓存存 随着访问量的增加,逐渐出现了许多⽤⼾访问同⼀部分内容的情况,对于这些⽐较热⻔的内容,没必要每次都从数据库读取。我 们可以使⽤缓存技术,例如可以使⽤google的开源缓存技术guava或者使⽤memcacahe作为应⽤层的缓存,也可以使⽤redis作 为数据库层的缓存。 另外,在某些场景下,关系型数据库并不是很适合,例如我想做⼀个“每⽇输⼊密码错误次数限制”的功能,思路⼤概是在⽤⼾ 登录时,如果登录错误,则记录下该⽤⼾的IP和错误次数,那么这个数据要放在哪⾥呢?假如放在内存中,那么显然会占⽤太⼤ 的内容;假如放在关系型数据库中,那么既要建⽴数据库表,还要简历对应的java bean,还要写SQL等等。⽽分析⼀下我们要存 储的数据,⽆⾮就是类似{ip:errorNumber}这样的key:value数据。对于这种数据,我们可以⽤NOSQL数据库来代替传统的关系 型数据库。 2、⻚⻚⾯⾯缓缓存存 除了数据缓存,还有⻚⾯缓存。⽐如使⽤HTML5的localstroage或者cookie。 优优点点:需要维护缓存服务器 提⾼了编码的复杂性 值值得得⼀⼀提提的的是是: 缓存集群的调度算法不同与上⾯提到的应⽤服务器和数据库。最好采⽤“⼀致性哈希算法”,这样才能提⾼命中率。这个就不展 开讲了,有兴趣的可以查阅相关资料。 加加⼊⼊缓缓存存后后的的结结构构:
我们的⽹站演进到现在,交易、商品、⽤⼾的数据都还在同⼀个数据库中。尽管采取了增加缓存,读写分离的⽅式,但随着数据 库的压⼒继续增加,数据库的瓶颈越来越突出,此时,我们可以有数据垂直拆分和⽔平拆分两种选择。 77…11、、数数据据垂垂直直拆拆分分 垂直拆分的意思是把数据库中不同的业务数据拆分道不同的数据库中,结合现在的例⼦,就是把交易、商品、⽤⼾的数据分开。 优优点点:解决了原来把所有业务放在⼀个数据库中的压⼒问题。 可以根据业务的特点进⾏更多的优化 缺缺点点:需要维护多个数据库 问问题题: 1. 需要考虑原来跨业务的事务 2. 跨数据库的join 解解决决问问题题⽅⽅案案: 1. 我们应该在应⽤层尽量避免跨数据库的事物,如果⾮要跨数据库,尽量在代码中控制。 2. 我们可以通过第三⽅应⽤来解决,如上⾯提到的mycat,mycat提供了丰富的跨库join⽅案,详情可参考mycat官⽅⽂档。 垂垂直直拆拆分分后后的的结结构构如如下下:
77…22、、数数据据⽔⽔平平拆拆分分 数据⽔平拆分就是把同⼀个表中的数据拆分到两个甚⾄多个数据库中。产⽣数据⽔平拆分的原因是某个业务的数据量或者更新量 到达了单个数据库的瓶颈,这时就可以把这个表拆分到两个或更多个数据库中。 优优点点:如果我们能客服以上问题,那么我们将能够很好地对数据量及写⼊量增⻓的情况。 问问题题: 1. 访问⽤⼾信息的应⽤系统需要解决SQL路由的问题,因为现在⽤⼾信息分在了两个数据库中,需要在进⾏数据操作时了解需要 操作的数据在哪⾥。 2. 主键的处理也变得不同,例如原来⾃增字段,现在不能简单地继续使⽤了。 3. 如果需要分⻚,就⿇烦了。 解解决决问问题题⽅⽅案案: 1. 我们还是可以通过可以解决第三⽅中间件,如mycat。 mycat可以通过SQL解析模块对我们的SQL进⾏解析,再根据我们的配置,把请求转发到具体的某个数据库。 2. 我们可以通过UUID保证唯⼀或⾃定义ID⽅案来解决。 3. mycat也提供了丰富的分⻚查询⽅案,⽐如先从每个数据库做分⻚查询,再合并数据做⼀次分⻚查询等等。 数数据据⽔⽔平平拆拆分分后后的的结结构构:
88…11、、拆拆分分应应⽤⽤ 随着业务的发展,业务越来越多,应⽤越来越⼤。我们需要考虑如何避免让应⽤越来越臃肿。这就需要把应⽤拆开,从⼀个应⽤ 变为俩个甚⾄更多。还是以我们上⾯的例⼦,我们可以把⽤⼾、商品、交易拆分开。变成“⽤⼾、商品”和“⽤⼾,交易”两个 ⼦系统。 拆分后的结构: 问问题题:
这样拆分后,可能会有⼀些相同的代码,如⽤⼾相关的代码,商品和交易都需要⽤⼾信息,所以在两个系统中都保留差不多的操作 ⽤⼾信息的代码。 如何保证这些代码可以复⽤是⼀个需要解决的问题。 解解决决问问题题: 1. 通过⾛服务化的路线来解决 88…22、、⾛⾛服服务务化化的的道道路路 为了解决上⾯拆分应⽤后所出现的问题,我们把公共的服务拆分出来,形成⼀种服务化的模式,简称SOA。 采⽤服务化之后的系统结构: 优优点点:相同的代码不会散落在不同的应⽤中了,这些实现放在了各个服务中⼼,使代码得到更好的维护。 我们把对数据库的交互放在了各个服务中⼼,让”前端“的web应⽤更注重与浏览器交互的⼯作。 问问题题: 随着⽹站的继续发展,我们的系统中可能出现不同语⾔开发的⼦模块和部署在不同平台的⼦系统。此时我们需要⼀个平台来传递
可靠的,与平台和语⾔⽆关的数据,并且能够把负载均衡透明化,能在调⽤过程中收集调⽤数据并分析之,推测出⽹站的访问增 ⻓率等等⼀系列需求,对于⽹站应该如何成⻓做出预测。开源消息中间件有阿⾥的dubbo,可以搭配Google开源的分布式程序协 调服务zookeeper实现服务器的注册与发现。 引⼊消息中间件后的结构: 以上的演变过程只是⼀个例⼦,并不适合所有的⽹站,实际中⽹站演进过程与⾃⾝业务和不同遇到的问题有密切的关系,没有固 定的模式。只有认真的分析和不断地探究,才能发现适合⾃⼰⽹站的架构。 本⽂有什么说错的地⽅,希望⼤家指出,让我好改正过来,多谢。 作作者者 || ⼩⼩MM的的博博客客 链链接接 || wwwwww…ccnnbbllooggss…ccoomm//xxiiaaooMMzzjjmm//pp//55222233779999…hhttmmll 参参考考:: 《⼤型⽹站技术架构:核⼼原理与案例分析》——李智慧 著 《⼤型⽹站系统与Java中间件实践》——曾宪杰 著 《MySQL性能调优与架构设计》——简朝阳 著 《keepalived权威指南》
阅读原⽂ 声明:pdf仅供学习使⽤,⼀切版权归原创公众号所有;建议持续关注原创公众号获取最新⽂章,学习愉快! 《mycat权威指南》 《dubbo⽤⼾指南》 《计算机⽹络》 《操作系统》 -E N D- 如果看到这⾥,说明你喜欢这篇⽂章,请转转发发、、点点赞赞。微信搜索「web_resource」,欢迎添加⼩编微信「focusoncode」,每 ⽇朋友圈更新⼀篇⾼质量技术博⽂(⽆⼴告)。 ↓扫扫描描⼆⼆维维码码添添加加⼩⼩编编↓↓ 推荐阅读 11… 狠!删库跑路! 22… 分布式与集群的区别究竟是什么? 33… 看完这篇 HTTP,⾯试官就难不倒你了 44… 快速建站利器!
⼀张图了解 Spring Cloud 微服务架构 SimpleEasy Java后端 2019-10-20 作者 | SimpleEasy 链接 | www.jianshu.com/p/84d2824980fe Spring cloud作为当下主流的微服务框架,让我们实现微服务架构简单快捷,Spring cloud中各个组件在微服务架构中扮演的⻆ ⾊如下图所⽰,⿊线表⽰注释说明,蓝线由A指向B,表⽰B从A处获取服务。 由上图所⽰微服务架构⼤致由上图的逻辑结构组成,其包括各种微服务、注册发现、服务⽹关、熔断器、统⼀配置、跟踪服务等。 下⾯说说Spring cloud中的组件分别充当其中的什么⻆⾊。 FFeeggiinn((接接⼝⼝调调⽤⽤))::微服务之间通过Rest接⼝通讯,Spring Cloud提供Feign框架来⽀持Rest的调⽤,Feign使得不同进程的Rest 接⼝调⽤得以⽤优雅的⽅式进⾏,这种优雅表现得就像同⼀个进程调⽤⼀样。 NNeettfflliixx eeuurreekkaa((注注册册发发现现))::微服务模式下,⼀个⼤的Web应⽤通常都被拆分为很多⽐较⼩的web应⽤(服务),这个时候就需要有
⼀个地⽅保存这些服务的相关信息,才能让各个⼩的应⽤彼此知道对⽅,这个时候就需要在注册中⼼进⾏注册。每个应⽤启动时 向配置的注册中⼼注册⾃⼰的信息(ip地址,端⼝号, 服务名称等信息),注册中⼼将他们保存起来,服务间相互调⽤的时候,通 过服务名称就可以到注册中⼼找到对应的服务信息,从⽽进⾏通讯。注册与发现服务为微服务之间的调⽤带来了⽅便,解决了硬 编码的问题。服务间只通过对⽅的服务id,⽽⽆需知道其ip和端⼝即可以获取对⽅⽅服务。 RRiibbbboonn((负负载载均均衡衡))::Ribbon是Netflix发布的负载均衡器,它有助于控制HTTP和TCP客⼾端的⾏为。为Ribbon,配置服务提供 者的地址列表后,Ribbon就可基于某种负载均衡算法,⾃动地帮助服务消费者去请求。Ribbon默认为我们提供了很多的负载均 衡算法,例如轮询、随机等。当然,我们也可为Ribbon实现⾃定义的负载均衡算法。在SpringCloud中,当Ribbon与Eureka配 合使⽤时,Ribbon可⾃动从EurekaServer获取服务提供者的地址列表,并基于负载均衡算法,请求其中⼀个服务提供者的实例 (为了服务的可靠性,⼀个微服务可能部署多个实例)。 tips:⼤家可以关注微信公众号:Java后端,获取更多优秀博⽂推送。 HHyyssttrriixx((熔熔断断器器))::当服务提供者响应⾮常缓慢,那么消费者对提供者的请求就会被强制等待,直到提供者响应或超时。在⾼负载 场景下,如果不做任何处理,此类问题可能会导致服务消费者的资源耗竭甚⾄整个系统的崩溃(雪崩效应)。Hystrix正是为了防 ⽌此类问题发⽣。Hystrix是由Netflix开源的⼀个延迟和容错库,⽤于隔离访问远程系统、服务或者第三⽅库,防⽌级联失败,从 ⽽提升系统的可⽤性与容错性。Hystrix主要通过以下⼏点实现延迟和容错。 包裹请求:使⽤HystrixCommand(或HystrixObservableCommand)包裹对依赖的调⽤逻辑,每个命令在独⽴线程中执⾏。 这使⽤了设计模式中的“命令模式”。 跳闸机制:当某服务的错误率超过⼀定阈值时,Hystrix可以⾃动或者⼿动跳闸,停⽌请求该服务⼀段时间。 资源隔离:Hystrix为每个依赖都维护了⼀个⼩型的线程池(或者信号量)。如果该线程池已满,发往该依赖的请求就被⽴即拒 绝,⽽不是排队等候,从⽽加速失败判定。 监控:Hystrix可以近乎实时地监控运⾏指标和配置的变化,例如成功、失败、超时和被拒绝的请求等。 回退机制:当请求失败、超时、被拒绝,或当断路器打开时,执⾏回退逻辑。回退逻辑可由开发⼈员指定。 ZZuuuull((微微服服务务⽹⽹关关)) ::不同的微服务⼀般会有不同的⽹络地址,⽽外部客⼾端可能需要调⽤多个服务的接⼝才能完成⼀个业务需 求。例如⼀个电影购票的⼿机APP,可能调⽤多个微服务的接⼝才能完成⼀次购票的业务流程,如果让客⼾端直接与各个微服务 通信,会有以下的问题: 客⼾端会多次请求不同的微服务,增加了客⼾端的复杂性。 存在跨域请求,在⼀定场景下处理相对复杂。 认证复杂,每个服务都需要独⽴认证。 难以重构,随着项⽬的迭代,可能需要重新划分微服务。例如,可能将多个服务合并成⼀个或者将⼀个服务拆分成多个。如果客 ⼾端直接与微服务通信,那么重构将很难实施。 某些微服务可能使⽤了对防⽕墙/浏览器不友好的协议,直接访问时会有⼀定的困难。 以上问题可借助微服务⽹关解决。微服务⽹关是介于客⼾端和服务器端之间的中间层,所有的外部请求都会先经过微服务⽹关。 使⽤微服务⽹关后,微服务⽹关将封装应⽤程序的内部结构,客⼾端只⽤跟⽹关交互,⽽⽆须直接调⽤特定微服务的接⼝。这 样,开发就可以得到简化。不仅如此,使⽤微服务⽹关还有以下优点: 易于监控。可在微服务⽹关收集监控数据并将其推送到外部系统进⾏分析。
易于认证。可在微服务⽹关上进⾏认证,然后再将请求转发到后端的微服务,⽽⽆须在每个微服务中进⾏认证。 减少了客⼾端与各个微服务之间的交互次数。 SSpprriinngg cclloouudd bbuuss(( 统统⼀⼀配配置置服服务务)):: 对于传统的单体应⽤,常使⽤配置⽂件管理所有配置。例如⼀个SpringBoot开发的单体 应⽤,可将配置内容放在application.yml⽂件中。如果需要切换环境,可设置多个Profile,并在启动应⽤时指定 spring.profiles.active={profile}。然⽽,在微服务架构中,微服务的配置管理⼀般有以下需求: 集中管理配置。⼀个使⽤微服务架构的应⽤系统可能会包含成百上千个微服务,因此集中管理配置是⾮常有必要的。 不同环境,不同配置。例如,数据源配置在不同的环境(开发、测试、预发布、⽣产等)中是不同的。 运⾏期间可动态调整。例如,可根据各个微服务的负载情况,动态调整数据源连接池⼤⼩或熔断阈值,并且在调整配置时不停⽌ 微服务。 配置修改后可⾃动更新。如配置内容发⽣变化,微服务能够⾃动更新配置。综上所述,对于微服务架构⽽⾔,⼀个通⽤的配置管 理机制是必不可少的,常⻅做法是使⽤配置服务器管理配置。Spring cloud bus利⽤Git或SVN等管理配置、采⽤Kafka或者 RabbitMQ等消息总线通知所有应⽤,从⽽实现配置的⾃动更新并且刷新所有微服务实例的配置。 SSlleeuutthh++ZZiippKKiinn((跟跟踪踪服服务务))::Sleuth和Zipkin结合使⽤可以通过图形化的界⾯查看微服务请求的延迟情况以及各个微服务的依赖 情况。需要注意的是Spring boot2及以上不在⽀持Zipkin的⾃定义,需要到官⽅⽹站下载ZipKin相关的jar包。 另外需要提⼀点的是SSpprriinngg bboooott aaccttuuaattoorr,,提供了很多监控端点如/actuator/info、/actuator/health、/acutator/refresh 等,可以查看微服务的信息、健康状况、刷新配置等。 -E N D- 如果看到这⾥,说明你喜欢这篇⽂章,请转转发发、、点点赞赞。微信搜索「web_resource」,关注后回复「进群」或者扫描下⽅⼆维 码即可进⼊⽆⼴告交流群。 ↓扫扫描描⼆⼆维维码码进进群群↓↓ 推荐阅读
阅读原⽂ 声明:pdf仅供学习使⽤,⼀切版权归原创公众号所有;建议持续关注原创公众号获取最新⽂章,学习愉快! 11… 感受 Lambda 之美,推荐收藏,需要时查阅 22… 如何优雅的导出 Excel 33… ⽂艺互联⽹公司 vs ⼆逼互联⽹公司 44… Java 开发中常⽤的 4 种加密⽅法 55… 团队开发中 Git 最佳实践 喜欢⽂章,点个在在看看
什么是微服务架构 Java后端 2019-08-29 点击上⽅蓝蓝⾊⾊字字体体,选择“标星公众号” 优质⽂章,第⼀时间送达 作者 | 古霜卡⽐ 链接 | w w w.cnblog s . com/ s k aby y 本⽂将介绍微服务架构和相关的组件,介绍他们是什么以及为什么要使⽤微服务架构和这些组件。本⽂侧重于简明 地表达微服务架构的全局图景,因此不会涉及具体如何使⽤组件等细节。 要理解微服务,⾸先要先理解不是微服务的那些。通常跟微服务相对的是单体应⽤,即将所有功能都打包成在⼀个独 ⽴单元的应⽤程序。从单体应⽤到微服务并不是⼀蹴⽽就的,这是⼀个逐渐演变的过程。本⽂将以⼀个⽹上超市应⽤ 为例来说明这⼀过程。 最最初初的的需需求求 ⼏年前,⼩明和⼩⽪⼀起创业做⽹上超市。⼩明负责程序开发,⼩⽪负责其他事宜。当时互联⽹还不发达,⽹上超市还 是蓝海。只要功能实现了就能随便赚钱。所以他们的需求很简单,只需要⼀个⽹站挂在公⽹,⽤⼾能够在这个⽹站上 浏览商品、购买商品;另外还需⼀个管理后台,可以管理商品、⽤⼾、以及订单数据。 我们整理⼀下功能清单: ⽹站⽤⼾注册、登录功能 商品展⽰ 下单管理后台 ⽤⼾管理 商品管理 订单管理 由于需求简单,⼩明左⼿右⼿⼀个慢动作,⽹站就做好了。管理后台出于安全考虑,不和⽹站做在⼀起,⼩明右⼿左⼿ 慢动作重播,管理⽹站也做好了。总体架构图如下:
⼩明挥⼀挥⼿,找了家云服务部署上去,⽹站就上线了。上线后好评如潮,深受各类肥宅喜爱。⼩明⼩⽪美滋滋地开始 躺着收钱。 随随着着业业务务发发展展 好景不⻓,没过⼏天,各类⽹上超市紧跟着拔地⽽起,对⼩明⼩⽪造成了强烈的冲击。 在竞争的压⼒下,⼩明⼩⽪决定开展⼀些营销⼿段: 开展促销活动。⽐如元旦全场打折,春节买⼆送⼀,情⼈节狗粮优惠券等等。 拓展渠道,新增移动端营销。除了⽹站外,还需要开发移动端APP,微信⼩程序等。 精准营销。利⽤历史数据对⽤⼾进⾏分析,提供个性化服务。 …… 这些活动都需要程序开发的⽀持。⼩明拉了同学⼩红加⼊团队。⼩红负责数据分析以及移动端相关开发。⼩明负责促 销活动相关功能的开发。 因为开发任务⽐较紧迫,⼩明⼩红没有好好规划整个系统的架构,随便拍了拍脑袋,决定把促销管理和数据分析放在 管理后台⾥,微信和移动端APP另外搭建。通宵了⼏天后,新功能和新应⽤基本完⼯。这时架构图如下: 这⼀阶段存在很多不合理的地⽅: ⽹站和移动端应⽤有很多相同业务逻辑的重复代码。 数据有时候通过数据库共享,有时候通过接⼝调⽤传输。接⼝调⽤关系杂乱。 单个应⽤为了给其他应⽤提供接⼝,渐渐地越改越⼤,包含了很多本来就不属于它的逻辑。应⽤边界模糊,功能归属混乱。 管理后台在⼀开始的设计中保障级别较低。加⼊数据分析和促销管理相关功能后出现性能瓶颈,影响了其他应⽤。 数据库表结构被多个应⽤依赖,⽆法重构和优化。 所有应⽤都在⼀个数据库上操作,数据库出现性能瓶颈。特别是数据分析跑起来的时候,数据库性能急剧下降。 开发、测试、部署、维护愈发困难。即使只改动⼀个⼩功能,也需要整个应⽤⼀起发布。有时候发布会不⼩⼼带上了⼀些未经 测试的代码,或者修改了⼀个功能后,另⼀个意想不到的地⽅出错了。为了减轻发布可能产⽣的问题的影响和线上业务停顿的 影响,所有应⽤都要在凌晨三四点执⾏发布。发布后为了验证应⽤正常运⾏,还得盯到第⼆天⽩天的⽤⼾⾼峰期…… 团队出现推诿扯⽪现象。关于⼀些公⽤的功能应该建设在哪个应⽤上的问题常常要争论很久,最后要么⼲脆各做各的,或者随
便放个地⽅但是都不维护。 尽管有着诸多问题,但也不能否认这⼀阶段的成果:快速地根据业务变化建设了系统。不过紧紧迫迫且且繁繁重重的的任任务务容容易易使使 ⼈⼈陷陷⼊⼊局局部部、、短短浅浅的的思思维维⽅⽅式式,,从从⽽⽽做做出出妥妥协协式式的的决决策策。在这种架构中,每个⼈都只关注在⾃⼰的⼀亩三分地,缺乏全 局的、⻓远的设计。⻓此以往,系统建设将会越来越困难,甚⾄陷⼊不断推翻、重建的循环。 是是时时候候做做出出改改变变了了 幸好⼩明和⼩红是有追求有理想的好⻘年。意识到问题后,⼩明和⼩红从琐碎的业务需求中腾出了⼀部分精⼒,开始 梳理整体架构,针对问题准备着⼿改造。 要做改造,⾸先你需要有⾜够的精⼒和资源。如果你的需求⽅(业务⼈员、项⽬经理、上司等)很强势地⼀⼼追求需 求进度,以致于你⽆法挪出额外的精⼒和资源的话,那么你可能⽆法做任何事…… 在编程的世界中,最重要的便是抽抽象象能能⼒⼒。微服务改造的过程实际上也是个抽象的过程。⼩明和⼩红整理了⽹上超市 的业务逻辑,抽象出公⽤的业务能⼒,做成⼏个公共服务: ⽤⼾服务 商品服务 促销服务 订单服务 数据分析服务 各个应⽤后台只需从这些服务获取所需的数据,从⽽删去了⼤量冗余的代码,就剩个轻薄的控制层和前端。这⼀阶段 的架构如下: 这个阶段只是将服务分开了,数据库依然是共⽤的,所以⼀些烟囱式系统的缺点仍然存在: 1. 数据库成为性能瓶颈,并且有单点故障的⻛险。 2. 数据管理趋向混乱。即使⼀开始有良好的模块化设计,随着时间推移,总会有⼀个服务直接从数据库取另⼀个服务的数据的现 象。 3. 数据库表结构可能被多个服务依赖,牵⼀发⽽动全⾝,很难调整。
如果⼀直保持共⽤数据库的模式,则整个架构会越来越僵化,失去了微服务架构的意义。因此⼩明和⼩红⼀⿎作⽓, 把数据库也拆分了。所有持久化层相互隔离,由各个服务⾃⼰负责。另外,为了提⾼系统的实时性,加⼊了消息队列机 制。架构如下: 完全拆分后各个服务可以采⽤异构的技术。⽐如数据分析服务可以使⽤数据仓库作为持久化层,以便于⾼效地做⼀ 些统计计算;商品服务和促销服务访问频率⽐较⼤,因此加⼊了缓存机制等。 还有⼀种抽象出公共逻辑的⽅法是把这些公共逻辑做成公共的框架库。这种⽅法可以减少服务调⽤的性能损耗。 但是这种⽅法的管理成本⾮常⾼昂,很难保证所有应⽤版本的⼀致性。 数据库拆分也有⼀些问题和挑战:⽐如说跨库级联的需求,通过服务查询数据颗粒度的粗细问题等。但是这些问 题可以通过合理的设计来解决。总体来说,数据库拆分是⼀个利⼤于弊的。 微服务架构还有⼀个技术外的好处,它使整个系统的分⼯更加明确,责任更加清晰,每个⼈专⼼负责为其他⼈提供更 好的服务。在单体应⽤的时代,公共的业务功能经常没有明确的归属。最后要么各做各的,每个⼈都重新实现了⼀遍; 要么是随机⼀个⼈(⼀般是能⼒⽐较强或者⽐较热⼼的⼈)做到他负责的应⽤⾥⾯。在后者的情况下,这个⼈在负责 ⾃⼰应⽤之外,还要额外负责给别⼈提供这些公共的功能⸺⽽这个功能本来是⽆⼈负责的,仅仅因为他能⼒较强/ ⽐较热⼼,就莫名地背锅(这种情况还被美其名⽈能者多劳)。结果最后⼤家都不愿意提供公共的功能。⻓此以往,团 队⾥的⼈渐渐变得各⾃为政,不再关⼼全局的架构设计。 从这个⻆度上看,使⽤微服务架构同时也需要组织结构做相应的调整。所以说做微服务改造需要管理者的⽀持。 改造完成后,⼩明和⼩红分清楚各⾃的锅。两⼈⼗分满意,⼀切就像是⻨克斯⻙⽅程组⼀样漂亮完美。 然⽽…… 没没有有银银弹弹 春天来了,万物复苏,⼜到了⼀年⼀度的购物狂欢节。眼看着⽇订单数量蹭蹭地上涨,⼩⽪⼩明⼩红喜笑颜开。可惜好
景不⻓,乐极⽣悲,突然嘣的⼀下,系统挂了。 以往单体应⽤,排查问题通常是看⼀下⽇志,研究错误信息和调⽤堆栈。⽽微微服服务务架架构构整整个个应应⽤⽤分分散散成成多多个个服服务务,,定定 位位故故障障点点⾮⾮常常困困难难。⼩明⼀个台机器⼀台机器地查看⽇志,⼀个服务⼀个服务地⼿⼯调⽤。经过⼗⼏分钟的查找,⼩ 明终于定位到故障点:促销服务由于接收的请求量太⼤⽽停⽌响应了。其他服务都直接或间接地会调⽤促销服务,于 是也跟着宕机了。在在微微服服务务架架构构中中,,⼀⼀个个服服务务故故障障可可能能会会产产⽣⽣雪雪崩崩效效⽤⽤,,导导致致整整个个系系统统故故障障。其实在节前,⼩明和⼩红 是有做过请求量评估的。按照预计,服务器资源是⾜以⽀持节⽇的请求量的,所以肯定是哪⾥出了问题。不过形势紧 急,随着每⼀分每⼀秒流逝的都是⽩花花的银⼦,因此⼩明也没时间排查问题,当机⽴断在云上新建了⼏台虚拟机, 然后⼀台⼀台地部署新的促销服务节点。⼏分钟的操作后,系统总算是勉强恢复正常了。整个故障时间内估计损失了 ⼏⼗万的销售额,三⼈的⼼在滴⾎…… 事后,⼩明简单写了个⽇志分析⼯具(量太⼤了,⽂本编辑器⼏乎打不开,打开了⾁眼也看不过来),统计了促销服务 的访问⽇志,发现在故障期间,商品服务由于代码问题,在某些场景下会对促销服务发起⼤量请求。这个问题并不复 杂,⼩明⼿指抖⼀抖,修复了这个价值⼏⼗万的Bug。 问题是解决了,但谁也⽆法保证不会再发⽣类似的其他问题。微服务架构虽然逻辑设计上看是完美的,但就像积⽊搭 建的华丽宫殿⼀样,经不起⻛吹草动。微服务架构虽然解决了旧问题,也引⼊了新的问题: 微服务架构整个应⽤分散成多个服务,定位故障点⾮常困难。 稳定性下降。服务数量变多导致其中⼀个服务出现故障的概率增⼤,并且⼀个服务故障可能导致整个系统挂掉。事实上,在⼤ 访问量的⽣产场景下,故障总是会出现的。 服务数量⾮常多,部署、管理的⼯作量很⼤。 开发⽅⾯:如何保证各个服务在持续开发的情况下仍然保持协同合作。 测试⽅⾯:服务拆分后,⼏乎所有功能都会涉及多个服务。原本单个程序的测试变为服务间调⽤的测试。测试变得更加复杂。 ⼩明⼩红痛定思痛,决⼼好好解决这些问题。对故障的处理⼀般从两⽅⾯⼊⼿,⼀⽅⾯尽量减少故障发⽣的概率,另 ⼀⽅⾯降低故障造成的影响。 监监控控 – 发发现现故故障障的的征征兆兆 在⾼并发分布式的场景下,故障经常是突然间就雪崩式爆发。所以必须建⽴完善的监控体系,尽可能发现故障的征 兆。微服务架构中组件繁多,各个组件所需要监控的指标不同。⽐如Redi s缓存⼀般监控占⽤内存值、⽹络流量,数据库 监控连接数、磁盘空间,业务服务监控并发数、响应延迟、错误率等。因此如果做⼀个⼤⽽全的监控系统来监控各个组 件是不⼤现实的,⽽且扩展性会很差。⼀般的做法是让各个组件提供报告⾃⼰当前状态的接⼝(met r i c s接⼝),这个 接⼝输出的数据格式应该是⼀致的。然后部署⼀个指标采集器组件,定时从这些接⼝获取并保持组件状态,同时提供 查询服务。最后还需要⼀个UI,从指标采集器查询各项指标,绘制监控界⾯或者根据阈值发出告警。 ⼤部分组件都不需要⾃⼰动⼿开发,⽹络上有开源组件。⼩明下载了Redi sExpor ter和M ySQLExpor ter,这两个 组件分别提供了Redi s缓存和M ySQL数据库的指标接⼝。微服务则根据各个服务的业务逻辑实现⾃定义的指标接 ⼝。然后⼩明采⽤Prometheus作为指标采集器,Grafana配置监控界⾯和邮件告警。这样⼀套微服务监控系统就 搭建起来了:
定定位位问问题题 – 链链路路跟跟踪踪 在微服务架构下,⼀个⽤⼾的请求往往涉及多个内部服务调⽤。为了⽅便定位问题,需要能够记录每个⽤⼾请求时, 微服务内部产⽣了多少服务调⽤,及其调⽤关系。这个叫做链路跟踪。 我们⽤⼀个I s tio⽂档⾥的链路跟踪例⼦来看看效果: 图⽚来⾃I s tio⽂档 从图中可以看到,这是⼀个⽤⼾访问produc tpage⻚⾯的请求。在请求过程中,produc tpage服务顺序调⽤了 detail s和rev ie w s服务的接⼝。⽽rev ie w s服务在响应过程中⼜调⽤了ratings的接⼝。整个链路跟踪的记录是⼀ 棵树:
要实现链路跟踪,每次服务调⽤会在HTTP的HEADERS中记录⾄少记录四项数据: traceId:traceId标识⼀个⽤⼾请求的调⽤链路。具有相同traceId的调⽤属于同⼀条链路。 spanId:标识⼀次服务调⽤的ID,即链路跟踪的节点ID。 parentId:⽗节点的spanId。 requestTime & responseTime:请求时间和响应时间。 另外,还需要调⽤⽇志收集与存储的组件,以及展⽰链路调⽤的UI组件。 以上只是⼀个极简的说明,关于链路跟踪的理论依据可详⻅Google的Dapper 了解了理论基础后,⼩明选⽤了Dapper的⼀个开源实现Zipkin。然后⼿指⼀抖,写了个HTTP请求的拦截器,在每 次HTTP请求时⽣成这些数据注⼊到HEADERS,同时异步发送调⽤⽇志到Zipkin的⽇志收集器中。这⾥额外提⼀ 下,HTTP请求的拦截器,可以在微服务的代码中实现,也可以使⽤⼀个⽹络代理组件来实现(不过这样⼦每个微服务 都需要加⼀层代理)。 链路跟踪只能定位到哪个服务出现问题,不能提供具体的错误信息。查找具体的错误信息的能⼒则需要由⽇志分析 组件来提供。 分分析析问问题题 – ⽇⽇志志分分析析 ⽇志分析组件应该在微服务兴起之前就被⼴泛使⽤了。即使单体应⽤架构,当访问数变⼤、或服务器规模增多时,⽇ 志⽂件的⼤⼩会膨胀到难以⽤⽂本编辑器进⾏访问,更糟的是它们分散在多台服务器上⾯。排查⼀个问题,需要登录
到各台服务器去获取⽇志⽂件,⼀个⼀个地查找(⽽且打开、查找都很慢)想要的⽇志信息。 因此,在应⽤规模变⼤时,我们需要⼀个⽇志的“搜搜索索引引擎擎”。以便于能准确的找到想要的⽇志。另外,数据源⼀侧还需 要收集⽇志的组件和展⽰结果的UI组件: ⼩明调查了⼀下,使⽤了⼤名⿍⿍地ELK⽇志分析组件。ELK是Elas ti c sear ch、Logs tash和Kibana三个组件的缩 写。Elasticsearch:搜索引擎,同时也是⽇志的存储。 Logstash:⽇志采集器,它接收⽇志输⼊,对⽇志进⾏⼀些预处理,然后输出到Elasticsearch。 Kibana:UI组件,通过Elasticsearch的API查找数据并展⽰给⽤⼾。 最后还有⼀个⼩问题是如何将⽇志发送到Logs tash。⼀种⽅案是在⽇志输出的时候直接调⽤Logs tash接⼝将⽇志 发送过去。这样⼀来⼜(咦,为啥要⽤“⼜”)要修改代码……于是⼩明选⽤了另⼀种⽅案:⽇志仍然输出到⽂件,每个 服务⾥再部署个Agent扫描⽇志⽂件然后输出给Logs tash。 ⽹⽹关关 – 权权限限控控制制,,服服务务治治理理 拆分成微服务后,出现⼤量的服务,⼤量的接⼝,使得整个调⽤关系乱糟糟的。经常在开发过程中,写着写着,忽然想 不起某个数据应该调⽤哪个服务。或者写歪了,调⽤了不该调⽤的服务,本来⼀个只读的功能结果修改了数据…… 为了应对这些情况,微服务的调⽤需要⼀个把关的东西,也就是⽹关。在调⽤者和被调⽤者中间加⼀层⽹关,每次调 ⽤时进⾏权限校验。另外,⽹关也可以作为⼀个提供服务接⼝⽂档的平台。 使⽤⽹关有⼀个问题就是要决定在多⼤粒度上使⽤:最粗粒度的⽅案是整个微服务⼀个⽹关,微服务外部通过⽹关 访问微服务,微服务内部则直接调⽤;最细粒度则是所有调⽤,不管是微服务内部调⽤或者来⾃外部的调⽤,都必须 通过⽹关。折中的⽅案是按照业务领域将微服务分成⼏个区,区内直接调⽤,区间通过⽹关调⽤。 由于整个⽹上超市的服务数量还不算特别多,⼩明采⽤的最粗粒度的⽅案:
服服务务注注册册与与发发现现 – 动动态态扩扩容容 前⾯的组件,都是旨在降低故障发⽣的可能性。然⽽故障总是会发⽣的,所以另⼀个需要研究的是如何降低故障产⽣ 的影响。 最粗暴的(也是最常⽤的)故障处理策略就是冗余。⼀般来说,⼀个服务都会部署多个实例,这样⼀来能够分担压⼒提 ⾼性能,⼆来即使⼀个实例挂了其他实例还能响应。 冗余的⼀个问题是使⽤⼏个冗余?这个问题在时间轴上并没有⼀个切确的答案。根据服务功能、时间段的不同,需要 不同数量的实例。⽐如在平⽇⾥,可能4个实例已经够⽤;⽽在促销活动时,流量⼤增,可能需要40个实例。因此冗余 数量并不是⼀个固定的值,⽽是根据需要实时调整的。 ⼀般来说新增实例的操作为: 1. 部署新实例 2. 将新实例注册到负载均衡或DNS上 操作只有两步,但如果注册到负载均衡或DNS的操作为⼈⼯操作的话,那事情就不简单了。想想新增40个实例后,要 ⼿⼯输⼊40个IP的感觉…… 解决这个问题的⽅案是服务⾃动注册与发现。⾸先,需要部署⼀个服务发现服务,它提供所有已注册服务的地址信息 的服务。DNS也算是⼀种服务发现服务。然后各个应⽤服务在启动时⾃动将⾃⼰注册到服务发现服务上。并且应⽤服 务启动后会实时(定期)从服务发现服务同步各个应⽤服务的地址列表到本地。服务发现服务也会定期检查应⽤服务 的健康状态,去掉不健康的实例地址。这样新增实例时只需要部署新实例,实例下线时直接关停服务即可,服务发现 会⾃动检查服务实例的增减。
服务发现还会跟客⼾端负载均衡配合使⽤。由于应⽤服务已经同步服务地址列表在本地了,所以访问微服务时,可以 ⾃⼰决定负载策略。甚⾄可以在服务注册时加⼊⼀些元数据(服务版本等信息),客⼾端负载则根据这些元数据进⾏ 流量控制,实现A/B测试、蓝绿发布等功能。 服务发现有很多组件可以选择,⽐如说Zookeeper 、Eureka、Consul、Et cd等。不过⼩明觉得⾃⼰⽔平不错,想炫 技,于是基于Redi s⾃⼰写了⼀个…… 熔熔断断、、服服务务降降级级、、限限流流 熔熔断断 当⼀个服务因为各种原因停⽌响应时,调⽤⽅通常会等待⼀段时间,然后超时或者收到错误返回。如果调⽤链路⽐较 ⻓,可能会导致请求堆积,整条链路占⽤⼤量资源⼀直在等待下游响应。所以当多次访问⼀个服务失败时,应熔断,标 记该服务已停⽌⼯作,直接返回错误。直⾄该服务恢复正常后再重新建⽴连接。
图⽚来⾃《微服务设计》 服服务务降降级级 当下游服务停⽌⼯作后,如果该服务并⾮核⼼业务,则上游服务应该降级,以保证核⼼业务不中断。⽐如⽹上超市下 单界⾯有⼀个推荐商品凑单的功能,当推荐模块挂了后,下单功能不能⼀起挂掉,只需要暂时关闭推荐功能即可。 限限流流 ⼀个服务挂掉后,上游服务或者⽤⼾⼀般会习惯性地重试访问。这导致⼀旦服务恢复正常,很可能因为瞬间⽹络流量 过⼤⼜⽴刻挂掉,在棺材⾥重复着仰卧起坐。因此服务需要能够⾃我保护⸺限流。限流策略有很多,最简单的⽐如当 单位时间内请求数过多时,丢弃多余的请求。另外,也可以考虑分区限流。仅拒绝来⾃产⽣⼤量请求的服务的请求。例 如商品服务和订单服务都需要访问促销服务,商品服务由于代码问题发起了⼤量请求,促销服务则只限制来⾃商品 服务的请求,来⾃订单服务的请求则正常响应。
测测试试 微服务架构下,测试分为三个层次: 1. 端到端测试:覆盖整个系统,⼀般在⽤⼾界⾯机型测试。 2. 服务测试:针对服务接⼝进⾏测试。 3. 单元测试:针对代码单元进⾏测试。 三种测试从上到下实施的容易程度递增,但是测试效果递减。端到端测试最费时费⼒,但是通过测试后我们对系统最 有信⼼。单元测试最容易实施,效率也最⾼,但是测试后不能保证整个系统没有问题。 由于端到端测试实施难度较⼤,⼀般只对核⼼功能做端到端测试。⼀旦端到端测试失败,则需要将其分解到单元测 试:则分析失败原因,然后编写单元测试来重现这个问题,这样未来我们便可以更快地捕获同样的错误。 服务测试的难度在于服务会经常依赖⼀些其他服务。这个问题可以通过M ock Ser ver解决:
单元测试⼤家都很熟悉了。我们⼀般会编写⼤量的单元测试(包括回归测试)尽量覆盖所有代码。 微微服服务务框框架架 指标接⼝、链路跟踪注⼊、⽇志引流、服务注册发现、路由规则等组件以及熔断、限流等功能都需要在应⽤服务上添加 ⼀些对接代码。如果让每个应⽤服务⾃⼰实现是⾮常耗时耗⼒的。基于DRY的原则,⼩明开发了⼀套微服务框架,将 与各个组件对接的代码和另外⼀些公共代码抽离到框架中,所有的应⽤服务都统⼀使⽤这套框架进⾏开发。 使⽤微服务框架可以实现很多⾃定义的功能。甚⾄可以将程序调⽤堆栈信息注⼊到链路跟踪,实现代码级别的链路 跟踪。或者输出线程池、连接池的状态信息,实时监控服务底层状态。 使⽤统⼀的微服务框架有⼀个⽐较严重的问题:框架更新成本很⾼。每次框架升级,都需要所有应⽤服务配合升级。 当然,⼀般会使⽤兼容⽅案,留出⼀段并⾏时间等待所有应⽤服务升级。但是如果应⽤服务⾮常多时,升级时间可能 会⾮常漫⻓。并且有⼀些很稳定⼏乎不更新的应⽤服务,其负责⼈可能会拒绝升级……因此,使⽤统⼀微服务框架需 要完善的版本管理⽅法和开发管理规范。 另另⼀⼀条条路路 – SSeerrvviiccee MMeesshh 另⼀种抽象公共代码的⽅法是直接将这些代码抽象到⼀个反向代理组件。每个服务都额外部署这个代理组件,所有 出站⼊站的流量都通过该组件进⾏处理和转发。这个组件被称为Sidecar。 Sidecar不会产⽣额外⽹络成本。Sidecar会和微服务节点部署在同⼀台主机上并且共⽤相同的虚拟⽹卡。所以 s idecar和微服务节点的通信实际上都只是通过内存拷⻉实现的。
图⽚来⾃:Pattern: Ser v i ce M esh Sidecar只负责⽹络通信。还需要有个组件来统⼀管理所有s idecar的配置。在Ser v i ce M esh中,负责⽹络通信的 部分叫数据平⾯(data plane),负责配置管理的部分叫控制平⾯(cont rol plane)。数据平⾯和控制平⾯构成了 Ser v i ce M esh的基本架构。 Sev i ce M esh相⽐于微服务框架的优点在于它不侵⼊代码,升级和维护更⽅便。它经常被诟病的则是性能问题。即 使回环⽹络不会产⽣实际的⽹络请求,但仍然有内存拷⻉的额外成本。另外有⼀些集中式的流量处理也会影响性能。 结结束束、、也也是是开开始始 微服务不是架构演变的终点。往细⾛还有Ser ver les s、FaaS等⽅向。另⼀⽅⾯也有⼈在唱合久必分分久必合,重新 发现单体架构…… 不管怎样,微服务架构的改造暂时告⼀段落了。⼩明满⾜地摸了摸⽇益光滑的脑袋,打算这个周末休息⼀下约⼩红喝 杯咖啡。 如果喜欢本篇⽂章,欢迎转发、点赞。关注订阅号「Web项⽬聚集地」,回复「全栈」即可获取 2019 年最新 Java、Python、前 端学习视频资源。 推推荐荐阅阅读读 11… 经常⽤ HashMap ?这 6 个问题回答下 ! 22… 数据库这么多锁,能锁住⼩姐姐吗?
声明:pdf仅供学习使⽤,⼀切版权归原创公众号所有;建议持续关注原创公众号获取最新⽂章,学习愉快! 33… ⼩⽩也能看懂,30 分钟搭建个⼈博客! 44… 快来薅当当的⽺⽑ ! 55… 聊⼀聊 Java 泛型中的通配符 66… 数据库不使⽤外键的 9 个理由 喜喜欢欢⽂⽂章章,,点点个个在在看看
今⽇头条技术架构分析 钟镇刚 Java后端 2019-12-13 点击上⽅ Java后端,选择 设为星标 优质⽂章,及时送达 作者 | 钟镇刚 链接 | blog.csdn.net/mucaoyx/article/details/84498468 今⽇头条创⽴于2012年3⽉,到⽬前仅4年时间。从⼗⼏个⼯程师开始研发,到上百⼈,再到200余⼈。产品线由内涵段⼦,到今 ⽇头条,今⽇特卖,今⽇电影等产品线。 ⼀、产品背景 今⽇头条是为⽤⼾提供个性化资讯客⼾端。下⾯就和⼤家分享⼀下当前今⽇头条的数据(据内部与公开数据综合): 5亿亿注注册册⽤⽤⼾⼾ 2014年5⽉1.5亿,2015年5⽉3亿,2016年5⽉份为5亿。⼏乎为成倍增⻓。 ⽇⽇活活44880000万万⽤⽤⼾⼾ 2014年为1000万⽇活,2015年为3000万⽇活。 ⽇⽇均均55亿亿PPVV 5亿⽂章浏览,视频为1亿。⻚⾯请求量超过30亿次。 ⽤⼾停留时⻓超过65分钟以上 1、⽂章抓取与分析 我们⽇常产⽣原创新闻在1万篇左右,包括各⼤新闻⽹站和地⽅站,另外还有⼀些⼩说,博客等⽂章。这些对于⼯程师来讲,写 个Crawler并⾮困难的事。 接下来,今⽇头条会⽤⼈⼯⽅式对敏感⽂章进⾏审核过滤。此外,今⽇头条头条号⽬前也有为数不少的原创⽂章加⼊到了内容遴 选队列中。 接下来我们会对⽂章进⾏⽂本分析,⽐如分类,标签、主题抽取,按⽂章或新闻所在地区,热度,权重等计算。 2、⽤⼾建模 当⽤⼾开始使⽤今⽇头条后,对⽤⼾动作的⽇志进⾏实时分析。使⽤的⼯具如下: - Scribe - Flume - Kafka
我们对⽤⼾的兴趣进⾏挖掘,会对⽤⼾的每个动作进⾏学习。主要使⽤: - Hadoop - Storm 产⽣的⽤⼾模型数据和⼤部分架构⼀样,保存在MySQL/MongoDB(读写分离)以及Memcache/Redis中。 随着⽤⼾量的不断扩展⼤,⽤⼾模型处理的机器集群数量较⼤。2015年前为7000台左右。其中,⽤⼾推荐模型包括以下维度: 1 ⽤⼾订阅 2 标签 3 部分⽂章打散推送 此时,需要每时每刻做推荐。 3、新⽤⼾的“冷启动” 今⽇头条会通过⽤⼾使⽤的⼿机,操作系统,版本等“识别”。另外,⽐如⽤⼾通过社交帐号登录,如新浪微博,头条会对其好 友,粉丝,微博内容及转发、评论等维度进⾏对⽤⼾做初步“画像”。 分析⽤⼾的主要参数如下: - 关注、粉丝关系 - 关系 - ⽤⼾标签 除了⼿机硬件,今⽇头条还会对⽤⼾安装的APP进⾏分析。例如机型和APP结合分析,⽤⼩⽶,⽤三星的和⽤苹果的不同,另外 还有⽤⼾浏览器的书签。头条会实时捕捉⽤⼾对APP频道的动作。另外还包括⽤⼾订阅的频道,⽐如电影,段⼦,商品等。 4、推荐系统 推荐系统,也称推荐引擎。它是今⽇头条技术架构的核⼼部分。包括⾃动推荐与半⾃动推荐系统两种类型: 1 ⾃动推荐系统 - ⾃动候选 - ⾃动匹配⽤⼾,如⽤⼾地址定位,抽取⽤⼾信息 - ⾃动⽣成推送任务 这时需要⾼效率,⼤并发的推送系统,上亿的⽤⼾都要收到。 2 半⾃动推荐系统 - ⾃动选择候选⽂章 - 根据⽤⼾站内外动作
头条的频道,在技术侧划分的包括分类频道、兴趣标签频道、关键词频道、⽂本分析等,这些都分成相对独⽴的开发团队。⽬前 已经有300+个分类器,仍在不断增加新的⽤⼾模型,原来的⽤⼾模型不⽤撤消,仍然发挥作⽤。 在还没有推出头条号时,内容主要是抓取其它平台的⽂章,然后去重,⼀年⼏百万级,并不太⼤。主要是⽤⼾动作⽇志收集,兴 趣收集,⽤⼾模型收集。 资讯App的技术指标,⽐如屏幕滑动,⽤⼾是不是对⼀篇都看完,停留时间等都需要我们特别关注 5、数据存储 今⽇头条使⽤MySQL或Mongo持久化存储+Memched(Redis),分了很多库(⼀个⼤内存库),亦尝试使⽤了SSD的产品。 今⽇头条的图⽚存储,直接放在数据库中,分布式保存⽂件,读取的时候采⽤CDN。 6、消息推送 消息推送,对于⽤⼾: 及时获取信息。对运营来讲,能够 提⾼⽤⽤⼾活跃度。⽐如在今⽇头条推送后能够提升20%左右的DAU, 如果没有推送,会影响10%左右 DAU(2015年数据)。 推送后要关注的ROI:点击率,点击量。能够监测到App卸载和推送禁⽤数量。 今⽇头条推送的主要内容包括突发与热点咨讯,有⼈评论回复,站外好友注册加⼊。 在头条,推送也是个性化: - 频率个性化 - 内容个性化 - 地域 - 兴趣
⽐如: 按照城市:辽宁朝阳发⽣的某个新闻事件,发给朝阳本地的⽤⼾。 按照兴趣:⽐如京东收购⼀号店,发给互联⽹兴趣的⽤⼾。 推送平台的⼯具和选择,需要具备如下的标准: - 通道,⾸先速度要快,但是要可控,可靠,并且节省资源 - 推送的速度要快,有不同维度的策略⽀持,可跟踪,开发接⼝要友好 - 推送运营的后台,反馈也要快,包括时效性,热度,⼯具操作⽅便 - 对于运营侧,清晰是否确定推荐,包括推送的⽂案处理 因此,推送后台应该提供⽇报,完整的数据后台,提供A/B Test⽅案⽀持。 推送系统⼀部分使⽤⾃有IDC,在发送量特别⼤,消耗带宽较严重。可以使⽤类似阿⾥云的服务,可有效节省成本。 ⼆、今⽇头条系统架构
三、头条微服务架构 今⽇头条通过拆分⼦系统,⼤的应⽤拆成⼩应⽤,抽象通⽤层做代码复⽤。 系统的分层⽐较典型。重点在基础设施,希望通过基础设施提⾼快速迭代、容灾和⼀系列的⼯作,希望各个业务团队能更快做业
务上的迭代以及架构上的调整。 四、今⽇头条的虚拟化PaaS平台规划 通过三层实现,通过 PaaS 平台统⼀管理。提供通⽤ SaaS 服务,同时提供通⽤的 App 执⾏引擎。最底层是 IaaS 层。 IaaS 管理所有的机器,把公有云整合起来,头条有⼀些热点事件会全国推⼴推送,对⽹络带宽⽐较⾼,我们借助公有云,需要哪 ⼀种类型计算资源,统⼀抽象起来。基础设施结合服务化的思路,⽐如⽇志,监控等等功能,业务不需要关注细节就可以享受到 基础设施提供的能⼒。 五、总结 今⽇头条重要的部分在于: 数据⽣成与采集 数据传输。Kafka做消息总线连接在线和离线系统。 数据⼊库。数据仓库、ETL(抽取转换加载) 数据计算。数据仓库中的数据表如何能被⾼效的查询很关键,因为这会直接关系到数据分析的效率。常⻅的查询引擎可以归 到三个模式中,Batch 类、MPP 类、Cube 类,头条在 3 种模式上都有所应⽤。 参考资料: 今⽇头条的核⼼架构解析 Go ‒ 今⽇头条架构 从⽆到有、从⼩到⼤,今⽇头条⼤数据平台实践经历的那些坑 今⽇头条推荐系统架构设计实践
收集资料,码字,整理,最后欢迎点击原⽂链接关注博主。【END】 如果看到这⾥,说明你喜欢这篇⽂章,请转转发发、、点点赞赞。微信搜索「web_resource」,关注后回复「进群」或者扫描下⽅⼆维码 即可进⼊⽆⼴告交流群。 ↓扫扫描描⼆⼆维维码码进进群群↓↓ 推荐阅读 11… 我把废旧 Android ⼿机改造成了 Linux 服务器 22… 动画: ⼀个浏览器是如何⼯作的? 33… 为什么你学不会递归? 44… ⼀个⼥⽣不主动联系你还有机会吗? 55… 团队开发中 Git 最佳实践
阅读原⽂ 声明:pdf仅供学习使⽤,⼀切版权归原创公众号所有;建议持续关注原创公众号获取最新⽂章,学习愉快! 喜欢⽂章,点个在在看看
前后端分离架构:Web 实现前后端分离,前后端解耦 ⼭河远阔 Java后端 2019-10-20 作者 | ⼭河远阔 来源 | www.blog.csdn.net/weixin_37539378 ⼀⼀、、前前⾔⾔ ”前后端分离“已经成为互联⽹项⽬开发的业界标杆,通过Tomcat+Ngnix(也可以中间有个Node.js),有效地进⾏解耦。并且前 后端分离会为以后的⼤型分布式架构、弹性计算架构、微服务架构、多端化服务(多种客⼾端,例如:浏览器,⻋载终端,安 卓,IOS等等)打下坚实的基础。 前后端分离(解耦)的核⼼思想是:前端Html⻚⾯通过Ajax调⽤后端的RestFul API并使⽤Json数据进⾏交互。 注:【在互联⽹架构中,web服务器:⼀般指像nginx,apache这类的服务器,他们⼀般只能解析静态资源。应⽤服务器:⼀ 般指像tomcat,jetty,resin这类的服务器可以解析动态资源也可以解析静态资源,但解析静态资源的能⼒没有web服务器 好。】 ⼀般只有Web服务器才能被外⽹访问,应⽤服务器只能内⽹访问。 ⼆⼆、、为为什什么么前前后后端端分分离离 ⼀般公司后端开发⼈员直接兼顾前端的⼯作,⼀边实现API接⼝,⼀边开发⻚⾯,两者互相切换着做,⽽且根据不同的url动态拼 接⻚⾯,这也导致后台的开发压⼒⼤⼤增加。前后端⼯作分配不均。不仅仅开发效率慢,⽽且代码难以维护。 ⽽前后端分离的话,则可以很好的解决前后端分⼯不均的问题,将更多的交互逻辑分配给前端来处理,⽽后端则可以专注于其本 职⼯作,⽐如提供API接⼝,进⾏权限控制以及进⾏运算⼯作。⽽前端开发⼈员则可以利⽤nodejs来搭建⾃⼰的本地服务器,直 接在本地开发,然后通过⼀些插件来将api请求转发到后台,这样就可以完全模拟线上的场景,并且与后台解耦。前端可以独⽴完 成与⽤⼾交互的整⼀个过程,两者都可以同时开⼯,不互相依赖,开发效率更快,⽽且分⼯⽐较均衡。 三三、、从从MMVVCC到到前前后后端端分分离离 MVC 是⼀种经典的设计模式,全名为 Model-View-Controller,即 模型-视图-控制器。 其中,模型 是⽤于封装数据的载体,例如,在 Java 中⼀般通过⼀个简单的 POJO(Plain Ordinary Java Object)来表⽰,其本 质是⼀个普通的 Java Bean,包含⼀系列的成员变量及其 getter/setter ⽅法。对于 视图 ⽽⾔,它更加偏重于展现,也就是说, 视图决定了界⾯到底⻓什么样⼦,在 Java 中可通过 JSP 来充当视图,或者通过纯 HTML 的⽅式进⾏展现,⽽后者才是⽬前的主 流。模型和视图需要通过 控制器 来进⾏粘合,例如,⽤⼾发送⼀个 HTTP 请求,此时该请求⾸先会进⼊控制器,然后控制器去获
取数据并将其封装为模型,最后将模型传递到视图中进⾏展现。 综上所述,MVC 的交互过程如下图所⽰: 也就是说,我们输⼊的是 AJAX 请求,输出的是 JSON 数据,市⾯上有这样的技术来实现这个功能吗?答案是 REST。 REST 全称是 Representational State Transfe(r 表述性状态转移),它是 Roy Fielding 博⼠在 2000 年写的⼀篇关于软件架构 ⻛格的论⽂,此⽂⼀出,威震四⽅!国内外许多知名互联⽹公司纷纷开始采⽤这种轻量级的 Web 服务,⼤家习惯将其称为 RESTful Web Services,或简称 REST 服务。 如果将浏览器这⼀端视为前端,⽽服务器那⼀端视为后端的话,可以将以上改进后的 MVC 模式简化为以下前后端分离模式: 可⻅,有了 REST 服务,前端关注界⾯展现,后端关注业务逻辑,分⼯明确,职责清晰。 四四、、认认识识RReesstt架架构构 REST 本质上是使⽤ URL 来访问资源种⽅式。众所周知,URL 就是我们平常使⽤的请求地址了,其中包括两部分:请求⽅式 与 请求路径,⽐较常⻅的请求⽅式是 GET 与 POST,但在 REST 中⼜提出了⼏种其它类型的请求⽅式,汇总起来有六种:GET、 POST、PUT、DELETE、HEAD、OPTIONS。 尤其是前四种,正好与CRUD(Create-Retrieve-Update-Delete,增删改查)四种操作相对应,例如,GET(查)、 POST(增)、PUT(改)、DELETE(删),这正是 REST 与 CRUD 的异曲同⼯之妙!需要强调的是,REST 是“⾯向资 源”的,这⾥提到的资源,实际上就是我们常说的领域对象,在系统设计过程中,我们经常通过领域对象来进⾏数据建模。
REST 是⼀个“⽆状态”的架构模式,因为在任何时候都可以由客⼾端发出请求到服务端,最终返回⾃⼰想要的数据,当前请求 不会受到上次请求的影响。也就是说,服务端将内部资源发布 REST 服务,客⼾端通过 URL 来访问这些资源,这不就是 SOA 所 提倡的“⾯向服务”的思想吗?所以,REST 也被⼈们看做是⼀种“轻量级”的 SOA 实现技术,因此在企业级应⽤与互联⽹应⽤ 中都得到了⼴泛应⽤。 下⾯我们举⼏个例⼦对 REST 请求进⾏简单描述: 可⻅,请求路径相同,但请求⽅式不同,所代表的业务操作也不同,例如,/advertiser/1 这个请求,带有 GET、PUT、DELETE 三种不同的请求⽅式,对应三种不同的业务操作。 虽然 REST 看起来还是很简单的,实际上我们往往需要提供⼀个 REST 框架,让其实现前后端分离架构,让开发⼈员将精⼒集中 在业务上,⽽并⾮那些具体的技术细节。 五五、、前前后后端端分分离离意意义义⼤⼤吗吗?? 1、该⽹站前端变化远⽐后端变化频繁,则意义⼤。 2、该⽹站尚处于原始开发模式,数据逻辑与表现逻辑混杂不清,则意义⼤。 3、该⽹站前端团队和后端团队分属两个领导班⼦,技能点差异很⼤,则意义⼤。 4、该⽹站前端效果绚丽/跨设备兼容要求⾼,则意义⼤。 六六、、术术业业有有专专攻攻((开开发发⼈⼈员员分分离离)) 以前的JavaWeb项⽬⼤多数都是java程序员⼜当爹⼜当妈,⼜搞前端(ajax/jquery/js/html/css等等),⼜搞后端 (java/mysql/oracle等等)。 随着时代的发展,渐渐的许多⼤中⼩公司开始把前后端的界限分的越来越明确,前端⼯程师只管前端的事情,后端⼯程师只管后 端的事情。 正所谓术业有专攻,⼀个⼈如果什么都会,那么他毕竟什么都不精。 ⼤中型公司需要专业⼈才,⼩公司需要全才,但是对于个⼈职业发展来说,我建议是分开。 对对于于后后端端jjaavvaa⼯⼯程程师师:: 把精⼒放在java基础,设计模式,jvm原理,spring+springmvc原理及源码,linux,mysql事务隔离与锁机 制,mongodb,http/tcp,多线程,分布式架构(dubbo,dubbox,spring cloud),弹性计算架构,微服务架构 (springboot+zookeeper+docker+jenkins),java性能优化,以及相关的项⽬管理等等。 后端追求的是:三⾼(⾼并发,⾼可⽤,⾼性能),安全,存储,业务等等。
对对于于前前端端⼯⼯程程师师:: 把精⼒放在html5,css3,jquery,angularjs,bootstrap,reactjs,vuejs,webpack,less/sass,gulp,nodejs,Google V8引擎,javascript多线程,模块化,⾯向切⾯编程,设计模式,浏览器兼容性,性能优化等等。 前端追求的是:⻚⾯表现,速度流畅,兼容性,⽤⼾体验等等。 tips:⼤家可以关注微信公众号:Java后端,获取更多优秀博⽂推送。 七七、、耦耦合合时时代代 ⼏曾何时,我们的JavaWeb项⽬都是使⽤了若⼲后台框架,springmvc/struts + spring + spring jdbc/hibernate/mybatis 等 等。⼤多数项⽬在java后端都是分了三层,控制层(controller/action),业务层(service/manage),持久层(dao)。 控制层负责接收参数,调⽤相关业务层,封装数据,以及路由&渲染到jsp⻚⾯。 然后jsp⻚⾯上使⽤各种标签(jstl/el/struts标签等)或者⼿写java表达式(<%=%>)将后台的数据展现出来,玩的是MVC那套 思路。 我们先看这种情况:需求定完了,代码写完了,测试测完了,然后呢?要发布了吧? 你需要⽤maven或者eclipse等⼯具把你的代码打成⼀个war包,然后把这个war包发布到你的⽣产环境下的web容器 (tomcat/jboss/weblogic/websphere/jetty/resin)⾥,对吧? 发布完了之后,你要启动你的web容器,开始提供服务,这时候你通过配置域名,dns等等相关,你的⽹站就可以访问了(假设 你是个⽹站)。 那我们来看,你的前后端代码是不是全都在那个war包⾥?包括你的js,css,图⽚,各种第三⽅的库,对吧? 好,下⾯在浏览器中输⼊你的⽹站域名(www.xxx.com),之后发⽣了什么?(这个问题也是很多公司的⾯试题) 我捡⼲的说了啊,基础不好的童鞋请⾃⼰去搜。 浏览器在通过域名通过dns服务器找到你的服务器外⽹ip,将http请求发送到你的服务器,在tcp3次握⼿之后(http下⾯是 tcp/ip),通过tcp协议开始传输数据,你的服务器得到请求后,开始提供服务,接收参数,之后返回你的应答给浏览器,浏览器 再通过content-type来解析你返回的内容,呈现给⽤⼾。 那么我们来看,我们先假设你的⾸⻚中有100张图⽚,此时,⽤⼾的看似⼀次http请求,其实并不是⼀次,⽤⼾在第⼀次访问的 时候,浏览器中不会有缓存,你的100张图⽚,浏览器要连着请求100次http请求(有⼈会跟我说http⻓连短连的问题,不在这 ⾥讨论),你的服务器接收这些请求,都需要耗费内存去创建socket来玩tcp传输(消耗你服务器上的计算资源)。 重点来了,这样的话,你的服务器的压⼒会⾮常⼤,因为⻚⾯中的所有请求都是只请求到你这台服务器上,如果1个⼈还好,如 果10000个⼈并发访问呢(先不聊服务器集群,这⾥就说是单实例服务器),那你的服务器能扛住多少个tcp连接?你的带宽有多 ⼤?你的服务器的内存有多⼤?你的硬盘是⾼性能的吗?你能抗住多少IO?你给web服务器分的内存有多⼤?会不会宕机? 这就是为什么,越是⼤中型的web应⽤,他们越是要解耦。 理论上你可以把你的数据库+应⽤服务+消息队列+缓存+⽤⼾上传的⽂件+⽇志+等等都扔在⼀台服务器上,你也不⽤玩什么服务 治理,也不⽤做什么性能监控,什么报警机制等等,就乱成⼀锅粥好了。
但是这样就好像是你把鸡蛋都放在⼀个篮⼦⾥,隐患⾮常⼤。如果因为⼀个⼦应⽤的内存不稳定导致整个服务器内存溢出⽽hung 住,那你的整个⽹站就挂掉了。 如果出意外挂掉,⽽恰好这时你们的业务⼜处于井喷式发展⾼峰期,那么恭喜你,业务成功被技术卡住,很可能会流失⼤量⽤ ⼾,后果不堪设想。 注意:技术⼀定是要⾛在业务前⾯的,否则你将错过最佳的发展期。 此外,你的应⽤全部都耦合在⼀起,相当于⼀个巨⽯,当服务端负载能⼒不⾜时,⼀般会使⽤负载均衡的⽅式,将服务器做成集 群,这样其实你是在⽔平扩展⼀块块巨⽯,性能加速度会越来越低,要知道,本⾝负载就低的功能or模块是没有必要⽔平扩展 的,在本⽂中的例⼦就是你的性能瓶颈不在前端,那⼲嘛要⽔平扩展前端呢??? 还有发版部署上线的时候,我明明只改了后端的代码,为什么要前端也跟着发布呢???(引⽤:《架构探险-轻量级微服务架 构》,⻩勇) 正常的互联⽹架构,是都要拆开的,你的web服务器集群,你的应⽤服务器集群+⽂件服务器集群+数据库服务器集群+消息队列 集群+缓存集群等等。 JJSSPP的的痛痛点点 以前的javaWeb项⽬⼤多数使⽤jsp作为⻚⾯层展⽰数据给⽤⼾,因为流量不⾼,因此也没有那么苛刻的性能要求,但现在是⼤数 据时代,对于互联⽹项⽬的性能要求是越来越⾼,因此原始的前后端耦合在⼀起的架构模式已经逐渐不能满⾜我们,因此我们需 要需找⼀种解耦的⽅式,来⼤幅度提升我们的负载能⼒。 1、动态资源和静态资源全部耦合在⼀起,服务器压⼒⼤,因为服务器会收到各种http请求,例如css的http请求,js的,图⽚的 等等。 ⼀旦服务器出现状况,前后台⼀起玩完,⽤⼾体验极差。 2、UI出好设计图后,前端⼯程师只负责将设计图切成html,需要由java⼯程师来将html套成jsp⻚⾯,出错率较⾼(因为⻚⾯ 中经常会出现⼤量的js代码), 修改问题时需要双⽅协同开发,效率低下。 3、jsp必须要在⽀持java的web服务器⾥运⾏(例如tomcat,jetty,resin等),⽆法使⽤nginx等(nginx据说单实例http并发 ⾼达5w,这个优势要⽤上), 性能提不上来。 4、第⼀次请求jsp,必须要在web服务器中编译成servlet,第⼀次运⾏会较慢。 5.每次请求jsp都是访问servlet再⽤输出流输出的html⻚⾯,效率没有直接使⽤html⾼(是每次哟,亲~)。 6、jsp内有较多标签和表达式,前端⼯程师在修改⻚⾯时会捉襟⻅肘,遇到很多痛点。 7、如果jsp中的内容很多,⻚⾯响应会很慢,因为是同步加载。 8、需要前端⼯程师使⽤java的ide(例如eclipse),以及需要配置各种后端的开发环境,你们有考虑过前端⼯程师的感受吗。 基于上述的⼀些痛点,我们应该把整个项⽬的开发权重往前移,实现前后端真正的解耦! 开开发发模模式式 以以前前⽼⽼的的⽅⽅式式是是::
产品经历/领导/客⼾提出需求 UI做出设计图 前端⼯程师做html⻚⾯ 后端⼯程师将html⻚⾯套成jsp⻚⾯(前后端强依赖,后端必须要等前端的html做好才能套jsp。如果html发⽣变更,就更 痛了,开发效率低) 集成出现问题 前端返⼯ 后端返⼯ ⼆次集成 集成成功 交付新新的的⽅⽅式式是是:: 产品经历/领导/客⼾提出需求 UI做出设计图 前后端约定接⼝&数据&参数 前后端并⾏开发(⽆强依赖,可前后端并⾏开发,如果需求变更,只要接⼝&参数不变,就不⽤两边都修改代码,开发效率 ⾼)前后端集成 前端⻚⾯调整 集成成功 交付 请请求求⽅⽅式式 以以前前⽼⽼的的⽅⽅式式是是:: 客⼾端请求 服务端的servlet或controller接收请求(后端控制路由与渲染⻚⾯,整个项⽬开发的权重⼤部分在后端) 调⽤service,dao代码完成业务逻辑 返回jsp jsp展现⼀些动态的代码
新新的的⽅⽅式式是是:: 浏览器发送请求 直接到达html⻚⾯(前端控制路由与渲染⻚⾯,整个项⽬开发的权重前移) html⻚⾯负责调⽤服务端接⼝产⽣数据(通过ajax等等,后台返回json格式数据,json数据格式因为简洁⾼效⽽取代xml) 填充html,展现动态效果,在⻚⾯上进⾏解析并操作DOM。 (有兴趣的童鞋可以访问⼀下阿⾥巴巴等⼤型⽹站,然后按⼀下F12,监控⼀下你刷新⼀次⻚⾯,他的http是怎么玩的,⼤多数 都是单独请求后台数据,使⽤json传输数据,⽽不是⼀个⼤⽽全的http请求把整个⻚⾯包括动+静全部返回过来) 总总结结⼀⼀下下新新的的⽅⽅式式的的请请求求步步骤骤:: ⼤量并发浏览器请求—>web服务器集群(nginx)—>应⽤服务器集群(tomcat)—>⽂件/数据库/缓存/消息队列服务器集群 同时⼜可以玩分模块,还可以按业务拆成⼀个个的⼩集群,为后⾯的架构升级做准备。 前前后后分分离离的的优优势势 1、可以实现真正的前后端解耦,前端服务器使⽤nginx。 前端/WEB服务器放的是css,js,图⽚等等⼀系列静态资源(甚⾄你还可以css,js,图⽚等资源放到特定的⽂件服务器,例如阿 ⾥云的oss,并使⽤cdn加速),前端服务器负责控制⻚⾯引⽤&跳转&路由,前端⻚⾯异步调⽤后端的接⼝,后端/应⽤服务器使 ⽤tomcat(把tomcat想象成⼀个数据提供者),加快整体响应速度。 这⾥需要使⽤⼀些前端⼯程化的框架⽐如nodejs,react,router,react,redux,webpack 2、发现bug,可以快速定位是谁的问题,不会出现互相踢⽪球的现象。 ⻚⾯逻辑,跳转错误,浏览器兼容性问题,脚本错误,⻚⾯样式等问题,全部由前端⼯程师来负责。 接⼝数据出错,数据没有提交成功,应答超时等问题,全部由后端⼯程师来解决。 双⽅互不⼲扰,前端与后端是相亲相爱的⼀家⼈。 3、在⼤并发情况下,我可以同时⽔平扩展前后端服务器,⽐如淘宝的⼀个⾸⻚就需要2000+台前端服务器做集群来抗住⽇均多少 亿+的⽇均pv。 去参加阿⾥的技术峰会,听他们说他们的web容器都是⾃⼰写的,就算他单实例抗10万http并发,2000台是2亿http并发,并 且他们还可以根据预知洪峰来⽆限拓展,很恐怖,就⼀个⾸⻚。。。 4、减少后端服务器的并发/负载压⼒ 除了接⼝以外的其他所有http请求全部转移到前端nginx上,接⼝的请求调⽤tomcat,参考nginx反向代理tomcat。 且除了第⼀次⻚⾯请求外,浏览器会⼤量调⽤本地缓存。 5、即使后端服务暂时超时或者宕机了,前端⻚⾯也会正常访问,只不过数据刷不出来⽽已。 6、也许你也需要有微信相关的轻应⽤,那样你的接⼝完全可以共⽤,如果也有app相关的服务,那么只要通过⼀些代码重构,也 可以⼤量复⽤接⼝,提升效率。(多端应⽤)
7、⻚⾯显⽰的东西再多也不怕,因为是异步加载。 8、nginx⽀持⻚⾯热部署,不⽤重启服务器,前端升级更⽆缝。 9、增加代码的维护性&易读性(前后端耦在⼀起的代码读起来相当费劲)。 10、提升开发效率,因为可以前后端并⾏开发,⽽不是像以前的强依赖。 11、在nginx中部署证书,外⽹使⽤https访问,并且只开放443和80端⼝,其他端⼝⼀律关闭(防⽌⿊客端⼝扫描), 内⽹使⽤http,性能和安全都有保障。 12、前端⼤量的组件代码得以复⽤,组件化,提升开发效率,抽出来! 注注意意事事项项 1、在开需求会议的时候,前后端⼯程师必须全部参加,并且需要制定好接⼝⽂档,后端⼯程师要写好测试⽤例(2个维度),不 要让前端⼯程师充当你的专职测试, 推荐使⽤chrome的插件postman或soapui或jmeter,service层的测试⽤例拿junit写。ps:前端也可以玩单元测试吗? 2、上述的接⼝并不是java⾥的interface,说⽩了调⽤接⼝就是调⽤你controler⾥的⽅法。 3、加重了前端团队的⼯作量,减轻了后端团队的⼯作量,提⾼了性能和可扩展性。 4、我们需要⼀些前端的框架来解决类似于⻚⾯嵌套,分⻚,⻚⾯跳转控制等功能。(上⾯提到的那些前端框架)。 5、如果你的项⽬很⼩,或者是⼀个单纯的内⽹项⽬,那你⼤可放⼼,不⽤任何架构⽽⾔,但是如果你的项⽬是外⽹项⽬,呵呵 哒。6、以前还有⼈在使⽤类似于velocity/freemarker等模板框架来⽣成静态⻚⾯,仁者⻅仁智者⻅智。 7、这篇⽂章主要的⽬的是说jsp在⼤型外⽹java web项⽬中被淘汰掉,可没说jsp可以完全不学,对于⼀些学⽣朋友来 说,jsp/servlet等相关的java web基础还是要掌握牢的,不然你以为springmvc这种框架是基于什么来写的? 8、如果⻚⾯上有⼀些权限等等相关的校验,那么这些相关的数据也可以通过ajax从接⼝⾥拿。 9、对于既可以前端做也可以后端做的逻辑,我建议是放到前端,为什么? 因为你的逻辑需要计算资源进⾏计算,如果放到后端去run逻辑,则会消耗带宽&内存&cpu等等计算资源,你要记住⼀点就是: 服务端的计算资源是有限的,⽽如果放到前端,使⽤的是客⼾端的计算资源,这样你的服务端负载就会下降(⾼并发场景)。 类似于数据校验这种,前后端都需要做! 10、前端需要有机制应对后端请求超时以及后端服务宕机的情况,友好的展⽰给⽤⼾。 扩扩展展阅阅读读 1、其实对于js,css,图⽚这类的静态资源可以考虑放到类似于阿⾥云的oss这类⽂件服务器上(如果是普通的服务器&操作系 统,存储在到达pb级的⽂件后,或者单个⽂件夹内的⽂件数量达到3-5万,io会有很严重的性能问题),再在oss上配cdn(全国 ⼦节点加速),这样你⻚⾯打开的速度像⻜⼀样, ⽆论你在全国的哪个地⽅,并且你的nginx的负载会进⼀步降低。 2、如果你要玩轻量级微服务架构,要使⽤nodejs做⽹关,⽤nodejs的好处还有利于seo优化,因为nginx只是向浏览器返回⻚⾯ 静态资源,⽽国内的搜索引擎爬⾍只会抓取静态数据,不会解析⻚⾯中的js,这使得应⽤得不到良好的搜索引擎⽀持。同时因为
nginx不会进⾏⻚⾯的组装渲染,需要把静态⻚⾯返回到浏览器,然后完成渲染⼯作,这加重了浏览器的渲染负担。 浏览器发起的请求经过nginx进⾏分发,URL请求统⼀分发到nodejs,在nodejs中进⾏⻚⾯组装渲染;API请求则直接发送到后 端服务器,完成响应。 3、如果遇到跨域问题,spring4的CORS可以完美解决,但⼀般使⽤nginx反向代理都不会有跨域问题,除⾮你把前端服务和后端 服务分成两个域名。 JSONP的⽅式也被淘汰掉了。 4、如果想玩多端应⽤,注意要去掉tomcat原⽣的session机制,要使⽤token机制,使⽤缓存(因为是分布式系统),做单点, 对于token机制的安全性问题,可以搜⼀下jwt。 5、前端项⽬中可以加⼊mock测试(构造虚拟测试对象来模拟后端,可以独⽴开发和测试),后端需要有详细的测试⽤例,保证 服务的可⽤性与稳定性。 总总结结前后端分离并⾮仅仅只是⼀种开发模式,⽽是⼀种架构模式(前后端分离架构)。 千万不要以为只有在撸代码的时候把前端和后端分开就是前后端分离了。需要区分前后端项⽬。前端项⽬与后端项⽬是两个项 ⽬,放在两个不同的服务器,需要独⽴部署,两个不同的⼯程,两个不同的代码库,不同的开发⼈员。 前后端⼯程师需要约定交互接⼝,实现并⾏开发,开发结束后需要进⾏独⽴部署,前端通过ajax来调⽤http请求调⽤后端的 restful api。 前端只需要关注⻚⾯的样式与动态数据的解析&渲染,⽽后端专注于具体业务逻辑。 -E N D- 如果看到这⾥,说明你喜欢这篇⽂章,请转转发发、、点点赞赞。微信搜索「web_resource」,关注后回复「进群」或者扫描下⽅⼆维 码即可进⼊⽆⼴告交流群。 ↓扫扫描描⼆⼆维维码码进进群群↓↓
阅读原⽂ 声明:pdf仅供学习使⽤,⼀切版权归原创公众号所有;建议持续关注原创公众号获取最新⽂章,学习愉快! 推荐阅读 11… 感受 Lambda 之美,推荐收藏,需要时查阅 22… 如何优雅的导出 Excel 33… ⽂艺互联⽹公司 vs ⼆逼互联⽹公司 44… Java 开发中常⽤的 4 种加密⽅法 55… 团队开发中 Git 最佳实践 喜欢⽂章,点个在在看看
后端架构设计:如何扛住100亿次请求? 搬砖的码哥 Java后端 2019-09-13 点击上⽅蓝蓝⾊⾊字字体体,选择“标星公众号” 优质⽂章,第⼀时间送达 链接:www.jianshu.com/p/0c04fcebf42a 11… 前前⾔⾔ 前⼏天,偶然看到了《扛住100亿次请求⸺如何做⼀个“有把握”的春晚红包系统”》⼀⽂,看完以后,感慨良多,收益很多。正所谓他 ⼭之⽯,可以攻⽟,虽然此⽂发表于2015年,我看到时已经过去良久,但是其中的思想仍然是可以为很多后端设计借鉴。 同时作为⼀微信后端⼯程师,看完以后⼜会思考,学习了这样的⽂章以后,是否能给⾃⼰的⼯作带来⼀些实际的经验呢?所谓纸上得 来终觉浅,绝知此事要躬⾏,能否⾃⼰实践⼀下100亿次红包请求呢?否则读完以后脑⼦⾥能剩下的东西 不过就是100亿 1400万 QPS整流 这样的字眼,剩下的⽂章将展⽰作者是如何以此过程为⽬标,在本地环境的模拟了此过程。 实现的⽬标:单机⽀持100万连接,模拟了摇红包和发红包过程,单机峰值QPS 6万,平稳⽀持了业务。 注:本⽂以及作者所有内容,仅代表个⼈理解和实践,过程和微信团队没有任何关系,真正的线上系统也不同,只是从⼀些技术点进⾏了 实践,请读者进⾏区分。 22… 背背景景知知识识 QPS:Queries per second 每秒的请求数⽬ PPS:Packets per second 每秒数据包数⽬ 摇红包:客⼾端发出⼀个摇红包的请求,如果系统有红包就会返回,⽤⼾获得红包 发红包:产⽣⼀个红包⾥⾯含有⼀定⾦额,红包指定数个⽤⼾,每个⽤⼾会收到红包信息,⽤⼾可以发送拆红包的请求,获取其 中的部分⾦额。 33… 确确定定⽬⽬标标 在⼀切系统开始以前,我们应该搞清楚我们的系统在完成以后,应该有⼀个什么样的负载能⼒。 33…11 ⽤⽤⼾⼾总总数数 通过⽂章我们可以了解到接⼊服务器638台,服务上限⼤概是14.3亿⽤⼾, 所以单机负载的⽤⼾上限⼤概是14.3亿/638台=228万 ⽤⼾/台。但是⽬前中国肯定不会有14亿⽤⼾同时在线,参考 http://qiye.qianzhan.com/show/detail/160818- b8d1c700.html的说法,2016年Q2 微信⽤⼾⼤概是8亿,⽉活在5.4 亿左右。所以在2015年春节期间,虽然使⽤的⽤⼾会很多,但 是同时在线肯定不到5.4亿。 33…22… 服服务务器器数数量量
⼀共有638台服务器,按照正常运维设计,我相信所有服务器不会完全上线,会有⼀定的硬件冗余,来防⽌突发硬件故障。假设⼀共有 600台接⼊服务器。 33…33 单单机机需需要要⽀⽀持持的的负负载载数数 每台服务器⽀持的⽤⼾数:5.4亿/600 = 90万。也就是平均单机⽀持90万⽤⼾。如果真实情况⽐90万更多,则模拟的情况可能会有 偏差,但是我认为QPS在这个实验中更重要。 33…44… 单单机机峰峰值值QQPPSS ⽂章中明确表⽰为1400万QPS.这个数值是⾮常⾼的,但是因为有600台服务器存在,所以单机的QPS为 1400万/600= 约为2.3万QPS, ⽂章 曾经提及系统可以⽀持4000万QPS,那么系统的QPS ⾄少要到4000万/600 = 约为 6.6万, 这个数值⼤约是⽬前的3倍,短期来看并不会被触 及。但是我相信应该做过相应的压⼒测试。 33…55… 发发放放红红包包 ⽂中提到系统以5万个每秒的下发速度,那么单机每秒下发速度50000/600 =83个/秒,也就是单机系统应该保证每秒以83个的速 度下发即可。 最后考虑到系统的真实性,还⾄少有⽤⼾登录的动作,拿红包这样的业务。真实的系统还会包括聊天这样的服务业务。 最后整体的看⼀下 100亿次摇红包这个需求,假设它是均匀地发⽣在春节联欢晚会的4个⼩时⾥,那么服务器的QPS 应该是 10000000000/600/3600/4.0=1157. 也就是单机每秒1000多次,这个数值其实并不⾼。如果完全由峰值速度1400万消化 10000000000/(140010000) = 714秒,也就是说只需要峰值坚持11分钟,就可以完成所有的请求。可⻅互联⽹产品的⼀个特点就 是峰值⾮常⾼,持续时间并不会很⻓。 总总结结从单台服务器看,它需要满⾜下⾯⼀些条件: ⽀持⾄少100万连接⽤⼾ 每秒⾄少能处理2.3万的QPS,这⾥我们把⽬标定得更⾼⼀些 分别设定到了3万和6万。 摇红包:⽀持每秒83个的速度下发放红包,也就是说每秒有2.3万次摇红包的请求,其中83个请求能摇到红包,其余的2.29万次请求 会知道⾃⼰没摇到。当然客⼾端在收到红包以后,也需要确保客⼾端和服务器两边的红包数⽬和红包内的⾦额要⼀致。因为没有⽀ 付模块,所以我们也把要求提⾼⼀倍,达到200个红包每秒的分发速度 ⽀持⽤⼾之间发红包业务,确保收发两边的红包数⽬和红包内⾦额要⼀致。同样也设定200个红包每秒的分发速度为我们的⽬标。 想完整模拟整个系统实在太难了,⾸先需要海量的服务器,其次需要上亿的模拟客⼾端。这对我来说是办不到,但是有⼀点可以确 定,整个系统是可以⽔平扩展的,所以我们可以模拟100万客⼾端,在模拟⼀台服务器 那么就完成了1/600的模拟。 和现有系统区别:和⼤部分⾼QPS测试的不同,本系统的侧重点有所不同。我对2者做了⼀些对⽐。
44… 基基础础软软件件和和硬硬件件 44…11软软件件 Golang 1.8r3 , shell, python (开发没有使⽤c++ ⽽是使⽤了golang, 是因为使⽤golang 的最初原型达到了系统要求。虽然golang 还存在 ⼀定的问题,但是和开发效率⽐,这点损失可以接受) 服务器操作系统:Ubuntu 12.04 客⼾端操作系统:debian 5.0 44…22硬硬件件环环境境 服务端:dell R2950。8核物理机,⾮独占有其他业务在⼯作,16G内存。这台硬件⼤概是7年前的产品,性能应该不是很⾼要求。 服务器硬件版本: 服务器CPU信息: 客⼾端:esxi 5.0 虚拟机,配置为4核 5G内存。⼀共17台,每台和服务器建⽴6万个连接。完成100万客⼾端模拟 55… 技技术术分分析析和和实实现现 5.1) 单机实现100万⽤⼾连接 这⼀点来说相对简单,笔者在⼏年前就早完成了单机百万⽤⼾的开发以及操作。现代的服务器都可以⽀持百万⽤⼾。相关内容可以查看: github代码以及相关⽂档: https://github.com/xiaojiaqi/C1000kPracticeGuide
系统配置以及优化⽂档: https://github.com/xiaojiaqi/C1000kPracticeGuide/tree/master/docs/cn 5.2) 3万QPS 这个问题需要分2个部分来看客⼾端⽅⾯和服务器⽅⾯。 客⼾端QPS 因为有100万连接连在服务器上,QPS为3万。这就意味着每个连接每33秒,就需要向服务器发⼀个摇红包的请求。因为单IP可以建⽴的连 接数为6万左右, 有17台服务器同时模拟客⼾端⾏为。我们要做的就保证在每⼀秒都有这么多的请求发往服务器即可。 其中技术要点就是客⼾端协同。但是各个客⼾端的启动时间,建⽴连接的时间都不⼀致,还存在⽹络断开重连这样的情况,各个客⼾端如 何判断何时⾃⼰需要发送请求,各⾃该发送多少请求呢? 我是这样解决的:利⽤NTP服务,同步所有的服务器时间,客⼾端利⽤时间戳来判断⾃⼰的此时需要发送多少请求。 算法很容易实现:假设有100万⽤⼾,则⽤⼾id 为0-999999.要求的QPS为5万, 客⼾端得知QPS为5万,总⽤⼾数为100万,它计算 100 万/5万=20,所有的⽤⼾应该分为20组,如果 time() % 20 == ⽤⼾id % 20,那么这个id的⽤⼾就该在这⼀秒发出请求,如此实现了多客⼾ 端协同⼯作。每个客⼾端只需要知道 总⽤⼾数和QPS 就能⾃⾏准确发出请求了。 (扩展思考:如果QPS是3万 这样不能被整除的数⽬,该如何办?如何保证每台客⼾端发出的请求数⽬尽量的均衡呢?) 服务器QPS 服务器端的QPS相对简单,它只需要处理客⼾端的请求即可。但是为了客观了解处理情况,我们还需要做2件事情。 第⼀:需要记录每秒处理的请求数⽬,这需要在代码⾥埋⼊计数器。 第⼆:我们需要监控⽹络,因为⽹络的吞吐情况,可以客观的反映出QPS的真实数据。为此,我利⽤python脚本 结合ethtool ⼯具编写了⼀个简单的⼯具,通过它我们可以直观的监视到⽹络的数据包通过情况如何。它可以客观的显⽰出我们的⽹络有如 此多的数据传输在发⽣。 ⼯具截图: 5.3) 摇红包业务 摇红包的业务⾮常简单,⾸先服务器按照⼀定的速度⽣产红包。红包没有被取⾛的话,就堆积在⾥⾯。服务器接收⼀个客⼾端的请求,如 果服务器⾥现在有红包就会告诉客⼾端有,否则就提⽰没有红包。 因为单机每秒有3万的请求,所以⼤部分的请求会失败。只需要处理好锁的问题即可。 我为了减少竞争,将所有的⽤⼾分在了不同的桶⾥。这样可以减少对锁的竞争。如果以后还有更⾼的性能要求,还可以使⽤ ⾼性能队列
——Disruptor来进⼀步提⾼性能。 注意,在我的测试环境⾥是缺少⽀付这个核⼼服务的,所以实现的难度是⼤⼤的减轻了。另外提供⼀组数字:2016年淘宝的双11的交易峰 值仅仅为12万/秒,微信红包分发速度是5万/秒,要做到这点是⾮常困难的。(http://mt.sohu.com/20161111/n472951708.shtml) 5.4) 发红包业务 发红包的业务很简单,系统随机产⽣⼀些红包,并且随机选择⼀些⽤⼾,系统向这些⽤⼾提⽰有红包。这些⽤⼾只需要发出拆红包的请 求,系统就可以随机从红包中拆分出部分⾦额,分给⽤⼾,完成这个业务。同样这⾥也没有⽀付这个核⼼服务。 5.5)监控 最后 我们需要⼀套监控系统来了解系统的状况,我借⽤了我另⼀个项⽬(https://github.com/xiaojiaqi/fakewechat) ⾥的部分代码完成 了这个监控模块,利⽤这个监控,服务器和客⼾端会把当前的计数器内容发往监控,监控需要把各个客⼾端的数据做⼀个整合和展⽰。同 时还会把⽇志记录下来,给以后的分析提供原始数据。线上系统更多使⽤opentsdb这样的时序数据库,这⾥资源有限,所以⽤了⼀个原始 的⽅案。 监控显⽰⽇志⼤概这样: 66… 代代码码实实现现及及分分析析 在代码⽅⾯,使⽤到的技巧实在不多,主要是设计思想和golang本⾝的⼀些问题需要考虑。 ⾸先golang的goroutine 的数⽬控制,因为⾄少有100万以上的连接,所以按照普通的设计⽅案,⾄少需要200万或者300万的 goroutine在⼯作。这会造成系统本⾝的负担很重。 其次就是100万个连接的管理,⽆论是连接还是业务都会造成⼀些⼼智的负担。 我的设计是这样的: ⾸先将100万连接分成多个不同的SET,每个SET是⼀个独⽴,平⾏的对象。每个SET 只管理⼏千个连接,如果单个SET ⼯作正常,我 只需要添加SET就能提⾼系统处理能⼒。 其次谨慎的设计了每个SET⾥数据结构的⼤⼩,保证每个SET的压⼒不会太⼤,不会出现消息的堆积。 再次减少了gcroutine的数⽬,每个连接只使⽤⼀个goroutine,发送消息在⼀个SET⾥只有⼀个gcroutine负责,这样节省了100 万个goroutine。这样整个系统只需要保留 100万零⼏百个gcroutine就能完成业务。⼤量的节省了cpu 和内存 系统的⼯作流程⼤概是:每个客⼾端连接成功后,系统会分配⼀个goroutine读取客⼾端的消息,当消息读取完成,将它转化为消息 对象放⾄在SET的接收消息队列,然后返回获取下⼀个消息。 在SET内部,有⼀个⼯作goroutine,它只做⾮常简单⽽⾼效的事情,它做的事情如下,检查SET的接受消息,它会收到3类消息: 客⼾端的摇红包请求消息
客⼾端的其他消息 ⽐如聊天 好友这⼀类 服务器端对客⼾端消息的回应 对于第1种消息客⼾端的摇红包请求消息 是这样处理的,从客⼾端拿到摇红包请求消息,试图从SET的红包队列⾥ 获取⼀个红包, 如果拿到了就把红包信息 返回给客⼾端,否则构造⼀个没有摇到的消息,返回给对应的客⼾端。 对于第2种消息客⼾端的其他消息 ⽐如聊天 好友这⼀类,只需简单地从队列⾥拿⾛消息,转发给后端的聊天服务队列即可,其他服 务会把消息转发出去。 对于第3种消息服务器端对客⼾端消息的回应。SET 只需要根据消息⾥的⽤⼾id,找到SET⾥保留的⽤⼾连接对象,发回去就可以 了。对于红包产⽣服务,它的⼯作很简单,只需要按照顺序在轮流在每个SET的红包产⽣对列⾥放⾄红包对象就可以了。这样可以保证每 个SET⾥都是公平的,其次它的⼯作强度很低,可以保证业务稳定。 github地址及代码⻅: https://github.com/xiaojiaqi/10billionhongbaos 77… 实实践践 实践的过程分为3个阶段 阶阶段段11 分别启动服务器端和监控端,然后逐⼀启动17台客⼾端,让它们建⽴起100万的链接。在服务器端,利⽤ss 命令 统计出每个客⼾端和服务 器建⽴了多少连接。 命令如下: 结果如下: 1 Alias ss2=Ss –ant | grep 1025 | grep EST | awk –F: “{print $8}” | sort | uniq – c’
阶阶段段22 利⽤客⼾端的http接⼝,将所有的客⼾端QPS 调整到3万,让客⼾端发出3W QPS强度的请求。 运⾏如下命令: 观察⽹络监控和监控端反馈,发现QPS 达到预期数据,⽹络监控截图: 在服务器端启动⼀个产⽣红包的服务,这个服务会以200个每秒的速度下发红包,总共4万个。此时观察客⼾端在监控上的⽇志,会发现基 本上以200个每秒的速度获取到红包。 等到所有红包下发完成后,再启动⼀个发红包的服务,这个服务系统会⽣成2万个红包,每秒也是200个,每个红包随机指定3位⽤⼾,并 向这3个⽤⼾发出消息,客⼾端会⾃动来拿红包,最后所有的红包都被拿⾛。
阶阶段段33 利⽤客⼾端的http接⼝,将所有的客⼾端QPS 调整到6万,让客⼾端发出6W QPS强度的请求。 如法炮制,在服务器端,启动⼀个产⽣红包的服务,这个服务会以200个每秒的速度下发红包。总共4万个。此时观察客⼾端在监控上 的⽇志,会发现基本上以200个每秒的速度获取到红包。 等到所有红包下发完成后,再启动⼀个发红包的服务,这个服务系统会⽣成2万个红包,每秒也是200个,每个红包随机指定3位⽤ ⼾,并向这3个⽤⼾发出消息,客⼾端会⾃动来拿红包,最后所有的红包都被拿⾛。 最后,实践完成。 88… 分分析析数数据据 在实践过程中,服务器和客⼾端都将⾃⼰内部的计数器记录发往监控端,成为了⽇志。我们利⽤简单python 脚本和gnuplt 绘图⼯具,将 实践的过程可视化,由此来验证运⾏过程。 第⼀张是客⼾端的QPS发送数据:
这张图的横坐标是时间,单位是秒,纵坐标是QPS,表⽰这时刻所有客⼾端发送的请求的QPS。 图的第⼀区间,⼏个⼩的峰值,是100万客⼾端建⽴连接的,图的第⼆区间是3万QPS 区间,我们可以看到数据 ⽐较稳定的保持在3万这个 区间。最后是6万QPS区间。但是从整张图可以看到QPS不是完美地保持在我们希望的直线上。这主要是以下⼏个原因造成的 当⾮常多goroutine 同时运⾏的时候,依靠sleep 定时并不准确,发⽣了偏移。我觉得这是golang本⾝调度导致的。当然如果 cpu⽐较强劲,这个现象会消失。 因为⽹络的影响,客⼾端在发起连接时,可能发⽣延迟,导致在前1秒没有完成连接。 服务器负载较⼤时,1000M⽹络已经出现了丢包现象,可以通过ifconfig 命令观察到这个现象,所以会有QPS的波动。 第⼆张是 服务器处理的QPS图: 和客⼾端的向对应的,服务器也存在3个区间,和客⼾端的情况很接近。但是我们看到了在⼤概22:57分,系统的处理能⼒就有⼀个明显 的下降,随后⼜提⾼的尖状。这说明代码还需要优化。 整体观察在3万QPS区间,服务器的QPS⽐较稳定,在6万QSP时候,服务器的处理就不稳定了。我相信这和我的代码有关,如果继续优化 的话,还应该能有更好的效果。 将2张图合并起来 :
基本是吻合的,这也证明系统是符合预期设计的。 这是红包⽣成数量的状态变化图: ⾮常的稳定。 这是客⼾端每秒获取的摇红包状态:
可以发现3万QPS区间,客⼾端每秒获取的红包数基本在200左右,在6万QPS的时候,以及出现剧烈的抖动,不能保证在200这个数 值了。我觉得主要是6万QPS时候,⽹络的抖动加剧了,造成了红包数⽬也在抖动。 最后是golang ⾃带的pprof 信息,其中有gc 时间超过了10ms, 考虑到这是⼀个7年前的硬件,⽽且⾮独占模式,所以还是可以接 受。总总结结按照设计⽬标,我们模拟和设计了⼀个⽀持100万⽤⼾,并且每秒⾄少可以⽀持3万QPS,最多6万QPS的系统,简单模拟了微信的摇 红包和发红包的过程。可以说达到了预期的⽬的。 如果600台主机每台主机可以⽀持6万QPS,只需要7分钟就可以完成 100亿次摇红包请求。 虽然这个原型简单地完成了预设的业务,但是它和真正的服务会有哪些差别呢?我罗列了⼀下
RReeffeerrss::单机百万的实践 https://github.com/xiaojiaqi/C1000kPracticeGuide 如何在AWS上进⾏100万⽤⼾压⼒测试 https://github.com/xiaojiaqi/fakewechat/wiki/Stress-Testing-in-the-Cloud 构建⼀个你⾃⼰的类微信系统 https://github.com/xiaojiaqi/fakewechat/wiki/Design http://djt.qq.com/article/view/1356 http://techblog.cloudperf.net/2016/05/2-million-packets-per-second-on-public.html http://datacratic.com/site/blog/1m-qps-nginx-and-ubuntu-1204-ec2 @⽕丁笔记 http://huoding.com/2013/10/30/296 https://gobyexample.com/non-blocking-channel-operations 如果喜欢本篇⽂章,欢迎转发、点赞。关注订阅号「Web项⽬聚集地」,回复「进群」即可进⼊⽆⼴告技术交流。 推荐阅读 11… Java 实现 QQ 登陆 22… 在阿⾥⼲了 5 年,⾯试个⼩公司挂了… 33… 如何写出让同事⽆法维护的代码? 44… 史上最烂的项⽬:苦撑12年,600 多万⾏代码 …
声明:pdf仅供学习使⽤,⼀切版权归原创公众号所有;建议持续关注原创公众号获取最新⽂章,学习愉快! 喜喜欢欢⽂⽂章章,,点点个个在在看看
基于 token 的多平台⾝份认证架构设计 Java后端 2⽉6⽇ 点击上⽅ Java后端,选择 设为星标 优质⽂章,及时送达 作者 | 哈莫 链接 | cnblogs.com/beer/p/6029861.html 11、、概概述述 在存在账号体系的信息系统中,对⾝份的鉴定是⾮常重要的事情。 随着移动互联⽹时代到来,客⼾端的类型越来越多, 逐渐出现了 ⼀个服务器,N 个客⼾端的格局 。 不同的客⼾端产⽣了不同的⽤⼾使⽤场景,这些场景: 有不同的环境安全威胁 不同的会话⽣存周期 不同的⽤⼾权限控制体系 不同级别的接⼝调⽤⽅式 综上所述,它们的⾝份认证⽅式也存在⼀定的区别。 本⽂将使⽤⼀定的篇幅对这些场景进⾏⼀些分析和梳理⼯作。 22、、使使⽤⽤场场景景 下⾯是⼀些在 IT 服务常⻅的⼀些使⽤场景: ⽤⼾在 web 浏览器端登录系统, 使⽤系统服务
⽤⼾在⼿机端(Android/iOS)登录系统, 使⽤系统服务 ⽤⼾使⽤开放接⼝登录系统, 调⽤系统服务 ⽤⼾在 PC 处理登录状态时通过⼿机扫码授权⼿机登录(使⽤得⽐较少) ⽤⼾在⼿机处理登录状态进通过⼿机扫码授权 PC 进⾏登录(⽐较常⻅) 通过对场景的细分, 得到如下不同的认证 token 类别: 1、原始账号密码类别 ⽤⼾名和密码 API 应⽤ ID/KEY 2、会话 ID 类别 浏览器端 token 移动端 token API 应⽤ token 3、接⼝调⽤类别 接⼝访问 token ⾝份授权类别 PC 和移动端相互授权的 token 33、、ttookkeenn 的的类类别别 不同场景的 token 进⾏如下⼏个维度的对⽐: 天天然然属属性性对对⽐⽐:: 1、使⽤成本 本认证⽅式在使⽤的时候, 造成的不便性。⽐如: 账号密码需要⽤⼾打开⻚⾯然后逐个键⼊ ⼆维码需要⽤⼾掏出⼿机进⾏扫码操作 2、变化成本 本认证⽅式, token 发⽣变化时, ⽤⼾需要做出的相应更改的成本: ⽤⼾名和密码发⽣变化时, ⽤⼾需要额外记忆和重新键⼊新密码 API 应⽤ ID/KEY 发⽣变化时, 第三⽅应⽤需要重新在代码中修改并部署 授权⼆维码发⽣变化时, 需要⽤⼾重新打开⼿机应⽤进⾏扫码 环境⻛险被偷窥的⻛险 被抓包的⻛险 被伪造的⻛险
可可调调控控属属性性对对⽐⽐:: 11、、使使⽤⽤频频率率 在⽹路中传送的频率 22、、有有效效时时间间 此 token 从创建到终结的⽣存时间 最终的⽬标: 安全和影响。 安全和隐私性主要体现在: token 不容易被窃取和盗⽤(通过对传送频率控制) token 即使被窃取, 产⽣的影响也是可控的(通过对有效时间控制) 关于隐私及隐私破坏后的后果, 有如下的基本结论: 曝光频率⾼的容易被截获 ⽣存周期⻓的在被截获后产⽣的影响更严重和深远 遵守如下原则: 变化成本⾼的 token 不要轻易变化 不轻易变化的 token 要减少曝光频率(⽹络传输次数) 曝光频率⾼的 token 的⽣存周期要尽量短 将各类 token 的固有特点及可控属性进⾏调控后, 对每个指标进⾏量化评分(1~5 分),我们可以得到如下的对⽐表: 备注: username/passwd 和 appid/app_key 是等价的效果 44、、ttookkeenn 的的层层级级关关系系 参考上⼀节的对⽐表,可以很容易对这些不同⽤途的 token 进⾏分层,主要可以分为 4 层: 密码层:最传统的⽤⼾和系统之间约定的数字⾝份认证⽅式
会话层:⽤⼾登录后的会话⽣命周期的会话认证 调⽤层:⽤⼾在会话期间对应⽤程序接⼝的调⽤认证 应⽤层:⽤⼾获取了接⼝访问调⽤权限后的⼀些场景或者⾝份认证应⽤ token 的分层图如下: 在⼀个多客⼾端的信息系统⾥⾯, 这些 token 的产⽣及应⽤的内在联系如下: ⽤⼾输⼊⽤⼾名和⽤⼾⼝令进⾏⼀次性认证 在 不同 的终端⾥⾯⽣成拥有 不同 ⽣命周期的会话 token 客⼾端会话 token 从服务端交换⽣命周期短但曝光 频繁 的接⼝访问 token 会话 token 可以⽣成和刷新延⻓ access_token 的⽣存时间 access_token 可以⽣成⽣存周期最短的⽤于授权的⼆维码的 token 使⽤如上的架构有如下的好处: 良好的统⼀性。可以解决不同平台上认证 token 的⽣存周期的 归⼀化 问题 良好的解耦性。核⼼接⼝调⽤服务器的认证 access_token 可以完成独⽴的实现和部署 良好的层次性。不同平台的可以有完全不同的⽤⼾权限控制系统,这个控制可以在 会话层 中各平台解决掉 44…11、、账账号号密密码码 ⼴义的 账号 / 密码 有如下的呈现⽅式: 传统的注册⽤⼾名和密码 应⽤程序的 appid/appkey 它它们们的的特特点点如如下下:: 11、、会会有有特特别别的的意意义义 ⽐如:⽤⼾⾃⼰为了⽅便记忆,会设置有⼀定含义的账号和密码。 22、、不不常常修修改改 账号密码对⽤⼾有特别含义,⼀般没有特殊情况不会愿意修改。⽽ appid/appkey 则会写在应⽤程序中,修改会意味着重新发布 上线的成本 33、、⼀⼀旦旦泄泄露露影影响响深深远远
正因为不常修改,只要泄露了基本相当于⽤⼾的⽹络⾝份被泄露,⽽且只要没被察觉这种⾝份盗⽤就会⼀直存在 所以在认证系统中应该尽量减少传输的机会,避免泄露。 44…22、、客客⼾⼾端端会会话话 ttookkeenn 功功能能:: 充当着 session 的⻆⾊,不同的客⼾端有不同的⽣命周期。 使使⽤⽤步步骤骤:: ⽤⼾使⽤账号密码,换取会话 token 不同的平台的 token 有不同的特点: WWeebb 平平台台⽣⽣存存周周期期短短 主要原因: 环境安全性:由于 web 登录环境⼀般很可能是公共环境,被他⼈盗取的⻛险值较⼤ 输⼊便捷性:在 PC 上使⽤键盘输⼊会⽐较便捷 移移动动端端⽣⽣存存周周期期⻓⻓ 主要原因: 环境安全性:移动端平台是个⼈⽤⼾极其私密的平台,它⼈接触的机会不⼤ 输⼊便捷性:在移动端上使⽤⼿指在⼩屏幕上触摸输⼊体验差,输⼊成本⾼ 44…33、、aacccceessss__ttookkeenn 功功能能:: 服务端应⽤程序 api 接⼝访问和调⽤的凭证。 使使⽤⽤步步骤骤:: 使⽤具有较⻓⽣命周期的会话 token 来换取此接⼝访问 token。 其曝光频率直接和接⼝调⽤频率有关,属于⾼频使⽤的凭证。为了照顾到隐私性,尽量减少其⽣命周期,即使被截取了,也不⾄ 于产⽣严重的后果。 注意:在客⼾端 token 之下还加上⼀个 access_token, 主要是为了让具有不同⽣命周期的客⼾端 token 最后在调⽤ api 的时候, 能够具有统⼀的认证⽅式。 44…44、、ppaamm__ttookkeenn 功功能能:: 由已经登录和认证的 PC 端⽣成的⼆维码的原始串号(Pc Auth Mobile)。 主主要要步步骤骤如如下下::
- PC 上⽤⼾已经完成认证,登录了系统 2. PC 端⽣成⼀组和此⽤⼾相关联的 pam_token 3. PC 端将此 pam_token 的使⽤链接⽣成⼆维码 4. 移动端扫码后,请求服务器,并和⽤⼾信息关联 5. 移动端获取 refresh_token(⻓时效的会话) 6. 根据 refreshtoken 获取 accesstoken 7. 完成正常的接⼝调⽤⼯作 备注:⽣存周期为 2 分钟, 2 分钟后过期删除 没有被使⽤时, 每 1 分钟变⼀次 被使⽤后, ⽴刻删除掉 此种认证模式⼀般不会被使⽤到 44…55、、mmaapp__ttookkeenn 功功能能:: 由已经登录的移动 app 来扫码认证 PC 端系统,并完成 PC 端系统的登录(Mobile Auth Pc)。 主主要要步步骤骤:: 1. 移动端完成⽤⼾⾝份的认证登录 app 2. 未登录的 PC ⽣成匿名的 map_token 3. 移动端扫码后在 db 中⽣成 map_token 和⽤⼾关联(完成签名) 4. db 同时针对此⽤⼾⽣成 web_token 5. PC 端⼀直以 maptoken 为参数查找此命名⽤⼾的 webtoken 6. PC 端根据 webtoken 去获取 accesstoken 7. 后续正常的调⽤接⼝调⽤⼯作 备注:⽣存周期为 2 分钟, 2 分钟后过期删除 没有被使⽤时, 每 1 分钟变⼀次 被使⽤后, ⽴刻删除掉 55、、⼩⼩结结与与展展望望 本⽂所设计的基于 token 的⾝份认证系统,主要解决了如下的问题: token 的分类问题 token 的隐私性参数设置问题 token 的使⽤场景问题 不同⽣命周期的 token 分层转化关系 本⽂中提到的设计⽅法,在 应⽤层 中可以适⽤于且不限于如下场景中:
声明:pdf仅供学习使⽤,⼀切版权归原创公众号所有;建议持续关注原创公众号获取最新⽂章,学习愉快! ⽤⼾登录 有时效的优惠券发放 有时效的邀请码发放 有时效的⼆维码授权 具有时效 ⼿机 / 邮件 验证码 多个不同平台调⽤同⼀套 API 接⼝ 多个平台使⽤同⼀个⾝份认证中⼼ ⾄于更多的使⽤场景,就需要⼤家去发掘了。 (完) 推荐阅读 11… 聊聊在阿⾥远程办公那点事⼉ 22… 你真的了解 volatile 吗? 33… 程序员才能看懂的动图 44… 如何获取靠谱的新型冠状病毒疫情
⼤型⽹站技术架构:摘要与读书笔记 芋道源码 Java后端 1⽉16⽇ 点击上⽅ Java后端,选择 设为星标 优质⽂章,及时送达 来源:xybaby cnblogs.com/xybaby/p/8907880.html 花了⼏个晚上看完了《⼤型⽹站技术架构》这本书,个⼈感觉这本书的⼴度还⾏,深度还有些⽋缺(毕竟只有200⻚左右)。 但是作为⼀个缺乏⼤型⽹站技术的IT⺠⼯,看完⼀遍还是很有收获的,⾄少对⼀个⽹站的技术演进、需要解决的问题有了⼀个 全⾯的认识。⽂中也有⼀些作者个⼈的⼼得、感悟、总结,我觉得还是很中肯的。 链接:https://book.douban.com/subject/25723064/ 在⽹上⼀搜,这本书的读书笔记还是很多的,⽽我⾃⼰还是决定写⼀篇读书笔记,主要是为了避免⾃⼰忘得太快。笔记的内容 并不完全按照原书的内容,主要记录的是我⾃⼰感兴趣的部分。 ⼀⼀个个⽹⽹站站的的进进化化史史 作者反复在⽂中提到⼀个观点:⼤型⽹站是根据业务需求逐步演化⽽来的,⽽不是设计出来的。 不得不承认,互联⽹⾏业发展到了今天,⼤⻥吃⼩⻥还是很普遍的,⼤公司的微创新能⼒分分钟就能⼲死⼀个⼩的项⽬,所以 ⼩公司需要⾜够快的发展,不停的快速迭代与试错。 下⾯是是⼀个演化的过程,图⽚来⾃⽹络。 初初始始阶阶段段的的⽹⽹站站架架构构 img 在初始阶段,访问量并不⼤,所以应⽤程序、数据库、⽂件等所有的资源都在⼀台服务器上。 应应⽤⽤服服务务和和数数据据服服务务分分离离
img 随着业务的发展,就会发现⼀台服务器抗不过来了,所以将应⽤服务器与数据(⽂件、数据库)服务器分离。三台服务器对硬 件资源的要求各不相同:应⽤服务器需要更快的CPU,⽂件服务器需要更⼤的磁盘和带宽,数据库服务器需要更快速的磁盘和 更⼤的内存。分离之后,三个服务器各司其职,也⽅便针对性的优化。 使使⽤⽤缓缓存存改改善善⽹⽹站站性性能能 img “世界上没有什么问题是加⼀级缓存解决不了的,如果有那就再加⼀级缓存” 缓存的使⽤⽆处不在,缓存的根本⽬的是加快访问速度。当数据库的访问压⼒过⼤的时候,就可以考虑使⽤缓存了。⽹站使⽤ 的缓存可以分为两种: 缓存在应⽤服务器上的本地缓存和缓存在专⻔的分布式缓存服务器上的远程缓存。 使使⽤⽤应应⽤⽤服服务务器器集集群群改改善善⽹⽹站站的的并并发发处处理理能能⼒⼒
img 随着业务的发展,单个应⽤服务器⼀定会成为瓶颈,应⽤服务器实现集群是⽹站可伸缩集群架构设计中较为简单成熟的⼀ 种。后⾯也会提到,将应⽤服务器设计为⽆状态的(没有需要保存的上下⽂信息),就可以通过增加机器,使⽤负载均衡来 scale out。 数数据据库库读读写写分分离离 img 即使使⽤了缓存,但在缓存未命中、或者缓存服务时效的情况下,还是需要访问数据库,这个时候就需要数据库的读写分 离:主库提供写操作,从库提供读服务。注意,在上图中增加了⼀个数据访问模块,可以对应⽤层透明数据库的主从分离信 息。
使使⽤⽤反反向向代代理理和和CCDDNN 加加速速⽹⽹站站晌晌应应 img CDN和反向代理其实都是缓存,区别在于CDN 部署在⽹络提供商的机房;⽽反向代理则部署在⽹站的中⼼机房。使⽤CDN 和 反向代理的⽬的都是尽旱返回数据给⽤⼾, ⼀⽅⾯加快⽤⼾访问速度,另⼀⽅⾯也减轻后端服务器的负载压⼒。 使使⽤⽤分分布布式式⽂⽂件件系系统统和和分分布布式式数数据据库库系系统统 img 单个物理机的磁盘是有限的,单个关系数据库的处理能⼒也是有上限的,所以需要分布式⽂件存储与分布式数据库。当然,也 需要”统⼀数据访问模块“,使得应⽤层不⽤关⼼⽂件、数据的具体位置。值得⼀提的事,关系型数据库⾃⾝并没有很好的⽔ 平扩展⽅案,因此⼀般都需要⼀个数据库代理层,如cobar、mycat。
使使⽤⽤NNooSSQQLL 和和搜搜索索引引擎擎 img web2.0的很多应⽤并⼀定适合⽤关系数据库存储,更加灵活的NoSql能更加⽅便的解决⼀些问题,⽽且NoSQL天然就⽀持分 布式。专⻔的搜索引擎在提供更优质服务的同时,也⼤⼤减轻了数据库的压⼒。 业业务务拆拆分分 img 将⼀个⽹站拆分成许多不同的应⽤, 每个应⽤独⽴部署维护。应⽤之间可以通过⼀个超链接建⽴关系(在⾸⻚上的导航链接每 个都指向不同的应⽤地址) ,也可以通过消息队列进⾏数据分发, 当然最多的还是通过访问同⼀个数据存储系统来构成⼀个关 联的完整系统 分分布布式式服服务务
img 既然每⼀个应⽤系统都需要执⾏许多相同的业务操作, ⽐如⽤⼾管理、商品管理等,那么可以将这些共⽤的业务提取出来,独 ⽴部署。 通过服务的分布式,各个应⽤能更好的独⽴发展,实现了从依赖模块到依赖服务的过渡。将通⽤的公共服务独⽴出来,也⽅便 做服务管控,⽐如对各个应⽤的服务请求进⾏监控,在⾼峰时期限制、关闭某些应⽤的访问等。 ⼤⼤型型⽹⽹站站架架构构模模式式与与核核⼼⼼要要素素 这⼀部分是说⼤型⽹站需要解决的核⼼问题,以及解决这些问题的常规思路。 核核⼼⼼要要素素 五个要点:性能,可⽤性,伸缩性,扩展性,安全 作者指出,很多时候⼤家都混淆了伸缩性(Scalability)与扩展性(Extensibility)。我以前也是把Scalability称之为扩展 性,不过想想,在我们讲代码质量的时候,扩展性也是指Extensibility,以后还是直接说这两个英⽂单词好了。 这⼏点后⾯会详细介绍。 ⽹⽹站站架架构构模模式式 对模式的定义,书中描述得很好: " 每⼀个模式描述了⼀个在我们周围不断重复发⽣的问题及该问题解决⽅案的核⼼。这样, 你就能⼀次⼜⼀次地使⽤该⽅案⽽ 不必做重复⼯作" 。模式的关键在于模式的可重复性, 问题与场景的可重复性带来解决⽅案的可重复使⽤。 ⽤我⾃⼰的话来说,模式就是套路。这些模式,都是为了达成上⾯提到的核⼼要素。那么,有哪些模式呢 分分层层分层是企业应⽤系统中最常⻅的⼀种架构模式,将系统在横向维度上切分成⼏个部分,每个部分负责⼀部分相对⽐较单⼀的职 责, 然后通过上层对下层的依赖和调⽤组成⼀个完整的系统。 在⼤型⽹站架构中也采⽤分层结构,将⽹主占软件系统分为应⽤层、服务层、数据层。 分层的好处在于:解耦合,独⽴发展,伸缩性,可扩展性。上⾯⽹站的进化史也凸出了分层的重要性。
但是分层架构也有⼀些挑战, 就是必须合理规划层次边界和接⼝,在开发过程中,严格遵循分层架构的约束, 禁⽌跨层次的 调⽤( 应⽤层直接调⽤数据层)及逆向调⽤(数据层调⽤服务层, 或者服务层调⽤应⽤层)。 分分割割分层强调的是横向切分,⽽分割是纵向切分, 上⾯⽹站进化史部分的业务拆分就包含了分割。 分割的⽬标是⾼内聚、低耦合的模块单元 分分布布式式 分层和分割的⼀个主要⽬的是分布式部署,但分布式也有⾃⼰的问题:⽹络通信带来的性能问题,可⽤性,⼀致性与分布式事 务,系统维护管理复杂度。 集集群群⼀个机器解决不了的问题,就⽤⼏个机器来解决,当服务⽆状态的时候,通过往集群增加机器就能解决⼤部分问题。对应⽹站 进化史中“使⽤应⽤服务器集群改善⽹站的并发处理能⼒” 缓缓存存缓存就是将数据存放在距离计算最近的位置以加快处理速度,同时⼤⼤减轻了数据提供者的压⼒ ⼤型⽹站架构设计在很多⽅⾯都使⽤了缓存设计:CDN、反向代理、本地缓存、分布式缓存 异异步步异步是解耦合的⼀个重要⼿段,常⻅的⽣产者-消费者模型就是⼀个异步模式。 出了解耦合,异步还能提⾼系统可⽤性、加快响应速度、流量削峰 冗冗余余冗余是系统可⽤性的重要保障,也是数据可靠性的重要⼿段 ⾃⾃动动化化 凡⼈总是会出这样那样的错误,能⾃动话的就要⾃动化。⾃动化⼤⼤解放了程序员、运维⼈员的⽣产⼒! 发布过程⾃动化、⾃动化代码管理、⾃动化测试、⾃动化安全检测、⾃动化部署、⾃动化监控、⾃动化报警、⾃动化失效转 移、⾃动化失效恢复、⾃动化降级。 性性能能奥运精神:更快、更⾼、更强 技术⼈员对于性能的追求是⽆⽌境的。 性能,站在不同的⻆度,衡量指标是不⼀样的: ⽤⼾视⻆:响应时间,优化⼿段:(浏览器优化,⻚⾯布局,压缩⽂件,http⻓链接),CND,反向代理 开发⼈员视⻆:系统延迟、吞吐量、稳定性。优化⼿段:缓存,异步,集群,代码优化 运维视⻆:基础设施性能 资源利⽤率。优化⼿段:定制⻣⼲⽹络、定制服务器,虚拟化 常⻅的衡量标准包括:响应时间、吞吐量、并发量。关于这些衡量标准,⽂中有⼀个很好的⽐喻:
系统吞吐量和系统并发数, 以及响应时间的关系可以形象地理解为⾼速公路的通⾏状况: 吞吐量是每天通过收费站的⻋辆 数⽬(可以换算成收费站收取的⾼速费) , 并发数是⾼速公路上的正在⾏驶的⻋辆数⽬,响应时间是⻋速。⻋辆很少时, ⻋速很快, 但是收到的⾼速费也相应较少; 随着⾼速公路上⻋辆数⽬的增多,⻋速略受影响,但是收到的⾼速费增加很快; 随着⻋辆的继续增加,⻋速变得越来越慢,⾼速公路越来越堵,收费不增反降; 如果⻋流量继续增加,超过某个极限后, 任何偶然因素都会导致⾼速全部瘫痪, ⻋⾛不动,费当然也收不着,⽽⾼速公路成了停⻋场(资源耗尽)。 wweebb前前端端性性能能优优化化 浏览器优化:减少http请求,浏览器缓存,压缩。CDN优化,反应代理 应应⽤⽤服服务务器器性性能能优优化化 四招:缓存、集群、异步、代码优化 缓缓存存⾸先⾃然是缓存 ⽹站性能优化第⼀定律: 优先考虑使⽤缓存优化性能。 使⽤缓存,需要考虑的是缓存置换与⼀致性问题,其中缓存⼀致性问题也是分布式系统中需要解决的⼀个问题,主要的解决⽅ 法有租期和版本号。 并不是所有的场合都适合缓存,如频繁修改的数据、没有热点访问的数据。 缓存的可⽤性:理论上不能完全依靠,但事实上尽可能⾼可⽤,否则数据库宕机导致系统不可⽤。因此缓存服务器也要纳⼊监 控,尽量⾼可⽤。 缓存穿透:如果因为不恰当的业务、或者恶意攻击持续⾼并发地请求某个不存在的数据,由于缓存没有保存该数据, 所有的请 求都会落到数据库上,会对数据库造成很⼤压⼒,甚⾄崩横。⼀个简单的对策是将不存在的数据也缓存起来(其value 值为null )。代代码码优优化化多线程为什么要使⽤多线程,IO阻塞 与 多核CPU 理想的load 是:即没有进程(线程)等待,也没有CPU空闲 启动线程数= [任务执⾏时间/ (任务执⾏时间-10 等待时间)J xCPU 内核数 资源复⽤这个很常⻅,各种池(pool):线程池、连接池 ⾼⾼可可⽤⽤ ⽹站年度可⽤性指标= ( 1-⽹站不可⽤时间/年度总时间) x lOO% 业界通常⽤N个9来衡量系统的可⽤性。如,2 个9 是基本可⽤, ⽹站年度不可⽤时间⼩于8 8 ⼩时; 3 个9是较⾼可⽤, ⽹站年 度不可⽤时间⼩于9 ⼩时; 4 个9 是具有⾃动恢复能⼒的⾼可⽤,⽹站年度不可⽤时间⼩于53 分钟; 5 个9 是极⾼可⽤性,⽹站 年度不可⽤时间⼩于5 分钟。
可⽤性是⼤型⽹站的命脉,是否可⽤,⽤⼾是可以⽴刻感知到的,短暂的不可⽤也会带来巨⼤的损失。这也是为什么⼤型⽹站 在⾯对CAP问题时,更看重A(avalibility)的原因。 ⾼可⽤架构的主要⼿段是数据和服务的冗余备份及失效转移。 在分层的⽹络架构中,通过保证每⼀层的⾼可⽤,就实现了整个系统的⾼可⽤。⽽每⼀层⼜有⾃⼰的⾼可⽤⼿段 应应⽤⽤层层⾼⾼可可⽤⽤ 位于应⽤层的服务器通常为了应对⾼并发的访问请求,会通过负载均衡设备将⼀组服务器组成⼀个集群共同对外提供服务,当 负载均衡设备通过⼼跳检测等⼿段监控到某台应⽤服务器不可⽤时,就将其从集群列表中剔除,并将请求分发到集群中其他可 ⽤的服务器上,使整个集群保持可⽤,从⽽实现应⽤⾼可⽤。 应⽤层的⾼可⽤很容易,因为应⽤服务器很多时候是⽆状态的。 但是也有时候需要有维护的数据,如session,这样就不能将⼀个请求路由到任意的应⽤服务器。要解决session的问题,有以 下⼏种⽅法: session绑定:利⽤负载均衡的源地址Hash 算法实现,负载均衡服务器总是将来源于同⼀IP 的请求分发到同⼀台服务器 上⽤cookie记录session:Cookie是存放在客⼾端(浏览器)的,在每次访问的时候带上cookie⾥⾯的信息即可 专⻔的session服务器:将应⽤服务器的状态分离, 分为⽆状态的应⽤服务器和有状态的Session。简单的⽅法是利⽤分 布式缓存、数据库(redis)来实现Session服务器的功能 服服务务层层的的⾼⾼可可⽤⽤ 服务层的⾼可⽤也是利⽤集群,不过需要借助分布式服务调⽤框架。 服务层的服务器被应⽤层通过分布式服务调⽤框架访问,分布式服务调⽤框架会在应⽤层客⼾端程序中实现软件负载均衡, 并 通过服务注册中⼼对提供服务的服务器进⾏⼼跳检测,发现有服务不可⽤,⽴即通知客⼾端程序修改服务访问列表,剔除不可 ⽤的服务器。 为了保证服务层的⾼可⽤,可以采⽤以下策略: 分层管理 超时设置 异步调⽤ 服务降级,包括:拒绝服务,⾼峰时段,拒绝低优先级应⽤的访问;关闭服务,关闭某些不重要的功能 幂等性设计,⽅便失败时重试 数数据据层层的的⾼⾼可可⽤⽤ 包括分布式⽂件系统与分布式数据库,核⼼都是冗余加失效转移。 冗余(复制集、replica)需要解决的核⼼问题是⼀致性问题 失效转移操作由三部分组成: 失效确认、访问转移、数据恢复。
img 上⾯描述了失效确认的两种⽅法:控制中⼼通过⼼跳检测存储服务器的存活性;应⽤在访问存储服务失败的时候通知控制中⼼ 检测存储服务存活性 伸伸缩缩性性((SSccaallaabbiilliittyy)) ⽹站的伸缩性是指不需要改变⽹站的软硬件设计,仅仅通过改变部署的服务器数量就可以扩⼤或者缩⼩⽹站的服务处理能⼒。 应应⽤⽤层层的的伸伸缩缩性性 将应⽤层设计成⽆状态,即可利⽤集群 + 负载均衡来解决伸缩性问题。 关于负载均衡,我之前也写过⼀篇⽂章《关于负载均衡的⼀切:总结与思考》 (http://www.cnblogs.com/xybaby/p/7867735.html)介绍。 缓缓存存的的伸伸缩缩性性 ⾸先,缓存是有状态的,分布式缓存服务器集群中不同服务器中缓存的数据各不相同,缓存访问请求不可以在缓存服务器集群 中的任意⼀台处理,必须先找到缓存有需要数据的服务器,然后才能访问。 如果缓存访问被路由到了没有缓存相关数据的服务器,那么该访问请求就会落地到数据库,增加数据库的压⼒。因此,必须让 新上线的缓存服务器对整个分布式缓存集群影响最⼩,即缓存命中率越⾼越好。 在这个场景下,最好的负载均衡算法就是⼀致性hash 数数据据层层的的伸伸缩缩性性 关系型数据库,依赖于分布式数据库代理。⽽NoSQL数据库产品都放弃了关系数据库的两⼤重要基础: 以关系代数为基础的结 构化查询语⾔( SQL ) 和事务⼀致性保证( AClD )。⽽强化其他⼀些⼤型⽹站更关注的特性: ⾼可⽤性和可伸缩性。 伸缩性总结:⼀个具有良好伸缩性架构设计的⽹站,其设计总是⾛在业务发展的前⾯, 在业务需要处理更多访问和服务之前, 就已经做好充⾜准备, 当业务需要时, 只需要购买或者租⽤服务器简单部署实施就可以。 可可扩扩展展性性((EExxtteennssiibbiilliittyy)) 设计⽹站可扩展架构的核⼼思想是模块化, 并在此基础之上, 降低模块间的耦合性,提⾼模块的复⽤性。
主要有分布式消息队列和分布式服务。 分布式消息队列通过消息对象分解系统耦合性, 不同⼦系统处理同⼀个消息。 分布式服务则通过接⼝分解系统辑合性, 不同⼦系统通过相同的接⼝描述进⾏服务调⽤。 分分布布式式服服务务 纵向拆分:将⼀个⼤应⽤拆分为多个⼩应⽤, 如果新增业务较为独⽴, 那么就直接将其设计部署为⼀个独⽴的Web 应⽤系 统。横向拆分: 将复⽤的业务拆分出来, 独⽴部署为分布式服务, 新增业务只需要调⽤这些分布式服务, 不需要依赖具体的模块代 码,即可快速搭建⼀个应⽤系统, ⽽模块内业务逻辑变化的时候, 只要接⼝保持⼀致就不会影响业务程序和其他模块。 分布式服务依赖于分布式服务治理框架 分分布布式式服服务务治治理理框框架架 这⼀块⼉接触甚少,还需要花点时间专⻔学习学习。 img 服务治理框架的功能和特点: 服务注册与发现 服务调⽤ 负载均衡 失效转移:分布式服务框架⽀持服务提供者的失效转移机制, 当某个服务实例不可⽤, 就将访问切换到其他服务实例 上,以实现服务整体⾼可⽤。 ⾼效远程通信 整合异构系统 对应⽤最⼩侵⼊ 版本管理:分布式服务框架需要⽀持服务多版本发布, 服务提供者先升级接⼝发布新版本的服务, 并同时提供旧版本的 服务供请求者调⽤, 当请求者调⽤接⼝升级后才可以关闭旧版本服务。 实时监控 OOtthheerrss 所谓问题, 就是体验⼀期望,当体验不能满⾜期望, 就会觉得出了问题。消除问题有两种⼿段:改善休验或者降低期望。 问题被发现,它只是问题发现者的问题,⽽不是问题拥有者的问题,如果想要解决⼀个问题,就必须提出这个问题,让问题的 拥有者知道问题的存在。 提出问题Tips: - 把" 我的问题" 表述成" 我们的问题" 2. 给上司提封闭式问题, 给下属提开放式问题 3. 指出问题⽽不是批评⼈ 4. ⽤赞同的⽅式提出问题 --》不是说 你这⾥有问题,⽽是说,⽅案不错,我有⼀点疑问(建议) 微信扫描⼆维码,关注我的公众号 阅读原⽂ 声明:pdf仅供学习使⽤,⼀切版权归原创公众号所有;建议持续关注原创公众号获取最新⽂章,学习愉快!
如何画好架构图 Java后端 3天前 点击上⽅ Java后端,选择 设为星标 优质⽂章,及时送达 来⾃公众号:阿⾥技术 导读:技术传播的价值,不仅仅体现在通过商业化产品和开源项⽬来缩短我们构建应⽤的路径,加速业务的上线速率,也体现 在优秀⼯程师在⼯作效率提升、产品性能优化和⽤⼾体验改善等经验⽅⾯的分享,以提⾼我们的专业能⼒。 接下来,阿⾥巴巴技术专家三画,将分享⾃⼰和团队在画好架构图⽅⾯的理念和经验,希望对你有所帮助。 当我们想⽤⼀张或⼏张图来描述我们的系统时,是不是经常遇到以下情况: 对着画布⽆从下⼿、删了⼜来? 如何⽤⼀张图描述我的系统,并且让产品、运营、开发都能看明⽩? 画了⼀半的图还不清楚受众是谁? 画出来的图到底是产品图功能图还是技术图⼜或是⼤杂烩? 图上的框框有点少是不是要找点⼉框框加进来? 布局怎么画都不满意…… 如果有同样的困惑,本⽂将介绍⼀种画图的⽅法论,来让架构图更清晰。 先先厘厘清清⼀⼀些些基基础础概概念念 11、、什什么么是是架架构构?? 架构就是对系统中的实体以及实体之间的关系所进⾏的抽象描述,是⼀系列的决策。 架构是结构和愿景。 系统架构是概念的体现,是对物/信息的功能与形式元素之间的对应情况所做的分配,是对元素之间的关系以及元素同周边环境之间 的关系所做的定义。 做好架构是个复杂的任务,也是个很⼤的话题,本篇就不做深⼊了。有了架构之后,就需要让⼲系⼈理解、遵循相关决策。 22、、什什么么是是架架构构图图?? 系统架构图是为了抽象地表⽰软件系统的整体轮廓和各个组件之间的相互关系和约束边界,以及软件系统的物理部署和软件系统的 演进⽅向的整体视图。 33、、架架构构图图的的作作⽤⽤ ⼀图胜千⾔。要让⼲系⼈理解、遵循架构决策,就需要把架构信息传递出去。架构图就是⼀个很好的载体。那么,画架构图是为 了:
解决沟通障碍 达成共识 减少歧义 44、、架架构构图图分分类类 搜集了很多资料,分类有很多,有⼀种⽐较流⾏的是4+1视图,分别为场景视图、逻辑视图、物理视图、处理流程视图和开发视 图。★ 场景视图 场景视图⽤于描述系统的参与者与功能⽤例间的关系,反映系统的最终需求和交互设计,通常由⽤例图表⽰。 ★ 逻辑视图 逻辑视图⽤于描述系统软件功能拆解后的组件关系,组件约束和边界,反映系统整体组成与系统如何构建的过程,通常由UML的组 件图和类图来表⽰。
★ 物理视图 物理视图⽤于描述系统软件到物理硬件的映射关系,反映出系统的组件是如何部署到⼀组可计算机器节点上,⽤于指导软件系统的 部署实施过程。 ★ 处理流程视图 处理流程视图⽤于描述系统软件组件之间的通信时序,数据的输⼊输出,反映系统的功能流程与数据流程,通常由时序图和流程图表 ⽰。
★ 开发视图 开发视图⽤于描述系统的模块划分和组成,以及细化到内部包的组成设计,服务于开发⼈员,反映系统开发实施过程。 以上 5 种架构视图从不同⻆度表⽰⼀个软件系统的不同特征,组合到⼀起作为架构蓝图描述系统架构。 怎怎样样的的架架构构图图是是好好的的架架构构图图 上⾯的分类是前⼈的经验总结,图也是从⽹上摘来的,那么这些图画的好不好呢?是不是我们要依葫芦画瓢去画这样⼀些图? 先不去管这些图好不好,我们通过对这些图的分类以及作⽤,思考了⼀下,总结下来,我们认为,在画出⼀个好的架构图之前, ⾸ 先应该要明确其受众,再想清楚要给他们传递什么信息 ,所以,不要为了画⼀个物理视图去画物理视图,为了画⼀个逻辑视图去画
逻辑视图,⽽应该根据受众的不同,传递的信息的不同,⽤图准确地表达出来,最后的图可能就是在这样⼀些分类⾥。那么,画出 的图好不好的⼀个直接标准就是:受众有没有准确接收到想传递的信息。 明确这两点之后,从受众⻆度来说,⼀个好的架构图是不需要解释的,它应该是⾃描述的,并且要具备⼀致性和⾜够的准确性,能 够与代码相呼应。 画画架架构构图图遇遇到到的的常常⻅⻅问问题题 11、、⽅⽅框框代代表表什什么么?? 为什么适⽤⽅框⽽不是圆形,它有什么特殊的含义吗?随意使⽤⽅框或者其它形状可能会引起混淆。 22、、虚虚线线、、实实线线什什么么意意思思??箭箭头头什什么么意意思思??颜颜⾊⾊什什么么意意思思?? 随意使⽤线条或者箭头可能会引起误会。 33、、运运⾏⾏时时与与编编译译时时冲冲突突??层层级级冲冲突突?? 架构是⼀项复杂的⼯作,只使⽤单个图表来表⽰架构很容易造成莫名其妙的语义混乱。 本本⽂⽂推推荐荐的的画画图图⽅⽅法法
C4 模型使⽤容器(应⽤程序、数据存储、微服务等)、组件和代码来描述⼀个软件系统的静态结构。这⼏种图⽐较容易画,也给出 了画图要点,但最关键的是,我们认为,它明确指出了每种图可能的受众以及意义。 下⾯的案例来⾃C4官⽹,然后加上了⼀些我们的理解,来看看如何更好的表达软件架构 11、、语语境境图图((SSyysstteemm CCoonntteexxtt DDiiaaggrraamm)) 这是⼀个想象的待建设的互联⽹银⾏系统,它使⽤外部的⼤型机银⾏系统存取客⼾账⼾、交易信息,通过外部电邮系统给客⼾发邮 件。可以看到,⾮常简单、清晰,相信不需要解释,都看的明⽩,⾥⾯包含了需要建设的系统本⾝,系统的客⼾,和这个系统有交 互的周边系统。 ★ ⽤途 这样⼀个简单的图,可以告诉我们,要构建的系统是什么;它的⽤⼾是谁,谁会⽤它,它要如何融⼊已有的IT环境。这个图的受众 可以是开发团队的内部⼈员、外部的技术或⾮技术⼈员。即:
构建的系统是什么 谁会⽤它 如何融⼊已有的IT环境 ★ 怎么画 中间是⾃⼰的系统,周围是⽤⼾和其它与之相互作⽤的系统。这个图的关键就是梳理清楚待建设系统的⽤⼾和⾼层次的依赖,梳理 清楚了画下来只需要⼏分钟时间。 22、、容容器器图图((CCoonnttaaiinneerr DDiiaaggrraamm)) 容器图是把语境图⾥待建设的系统做了⼀个展开。 上图中,除了⽤⼾和外围系统,要建设的系统包括⼀个基于java\spring mvc的web应⽤提供系统的功能⼊⼝,基于xamarin架构的 ⼿机app提供⼿机端的功能⼊⼝,⼀个基于java的api应⽤提供服务,⼀个mysql数据库⽤于存储,各个应⽤之间的交互都在箭头线 上写明了。 看这张图的时候,不会去关注到图中是直⻆⽅框还是圆⻆⽅框,不会关注是实线箭头还是虚线箭头,甚⾄箭头的指向也没有引起太 多注意。 我们有许多的画图⽅式,都对框、线的含义做了定义,这就需要画图的⼈和看图的⼈都清晰的理解这些定义,才能读全图⾥的信 息,⽽现实是,这往往是⾮常⾼的⼀个要求,所以,很多图只能看个⼤概的含义。 ★ ⽤途 这个图的受众可以是团队内部或外部的开发⼈员,也可以是运维⼈员。⽤途可以罗列为:
展现了软件系统的整体形态 体现了⾼层次的技术决策 系统中的职责是如何分布的,容器间的是如何交互的 告诉开发者在哪⾥写代码 ★ 怎么画 ⽤⼀个框图来表⽰,内部可能包括名称、技术选择、职责,以及这些框图之间的交互,如果涉及外部系统,最好明确边界。 33、、组组件件图图((CCoommppoonneenntt DDiiaaggrraamm)) 组件图是把某个容器进⾏展开,描述其内部的模块。 ★ ⽤途 这个图主要是给内部开发⼈员看的,怎么去做代码的组织和构建。其⽤途有: 描述了系统由哪些组件/服务组成 厘清了组件之间的关系和依赖 为软件开发如何分解交付提供了框架 44、、类类图图((CCooddee//CCllaassss DDiiaaggrraamm))
这个图很显然是给技术⼈员看的,⽐较常⻅,就不详细介绍了。 案案例例分分享享 下⾯是内部的⼀个实时数据⼯具的架构图。作为⼀个应该⾃描述的架构图,这⾥不多做解释了。如果有看不明⽩的,那肯定是还画 的不够好。 画好架构图可能有许多⽅法论,本篇主要介绍了C4这种⽅法,C4的理论也是不断进化的。但不论是哪种画图⽅法论,我们回到画图 初衷,更好的交流,我们在画的过程中不必被条条框框所限制。简⽽⾔之,画之前想好:画图给谁看,看什么,怎么样不解释就看 懂。作者简介:三画,阿⾥巴巴技术专家,梓敬、鹏升和余乐对此⽂亦有贡献。三画曾多年从事⼯作流引擎研发⼯作,现专注于⾼并发移动互联 ⽹应⽤的架构和开发,和本⽂贡献者均来⾃阿⾥巴巴零售通部⻔。⽬前零售通⼤量招Java开发,欢迎有志之⼠投简历到 lst-
声明:pdf仅供学习使⽤,⼀切版权归原创公众号所有;建议持续关注原创公众号获取最新⽂章,学习愉快! wireless@alibaba-inc.com,和我们⼀起共建智能分销⽹络,让百万⼩店拥抱DT时代。 参考资料: C4官⽹:https://c4model.com/ 为什么需要软件架构图: https://www.infoq.cn/article/GhprrUlOYyOqS8*FR1pH 书籍:《程序员必读之软件架构》 -E N D- 如果看到这⾥,说明你喜欢这篇⽂章,请转转发发、、点点赞赞。同时标星(置顶)本公众号可以第⼀时间接受到博⽂推送。 推荐阅读 11… 三个统⼀ 22… 今天聊聊 Java 序列化 33… 41 道 Spring Boot ⾯试题,帮你整理好了! 44… Zookeeper ⼊⻔⽂章
⽀付宝架构师眼中的⾼并发架构 Java后端 2019-11-25 点击上⽅ Java后端,选择 设为星标 优质⽂章,及时送达 作者 | sflaqiu 链接 | http://blog.thankbabe.com 前前⾔⾔⾼并发经常会发⽣在有⼤活跃⽤⼾量,⽤⼾⾼聚集的业务场景中,如:秒杀活动,定时领取红包等。 为了让业务可以流畅的运⾏并且给⽤⼾⼀个好的交互体验,我们需要根据业务场景预估达到的并发量等因素,来设计适合 ⾃⼰业务场景的⾼并发处理⽅案。 在电商相关产品开发的这些年,我有幸的遇到了并发下的各种坑,这⼀路摸爬滚打过来有着不少的⾎泪史,这⾥进⾏的总 结,作为⾃⼰的归档记录,同时分享给⼤家。 服服务务器器架架构构 业务从发展的初期到逐渐成熟,服务器架构也是从相对单⼀到集群,再到分布式服务。 ⼀个可以⽀持⾼并发的服务少不了好的服务器架构,需要有均衡负载,数据库需要主从集群,nosql缓存需要主从集群,静 态⽂件需要上传cdn,这些都是能让业务程序流畅运⾏的强⼤后盾。 服务器这块多是需要运维⼈员来配合搭建,具体我就不多说了,点到为⽌。 ⼤致需要⽤到的服务器架构如下: 服务器均衡负载(如:nginx,阿⾥云SLB) 资源监控 分布式 数据库主从分离,集群 DBA 表优化,索引优化,等 分布式 nosql 主从分离,集群 主从分离,集群 主从分离,集群 redis
mongodb memcache cdnhtml css jsimage 并并发发测测试试 ⾼并发相关的业务,需要进⾏并发的测试,通过⼤量的数据分析评估出整个架构可以⽀撑的并发量。 测试⾼并发可以使⽤第三⽅服务器或者⾃⼰测试服务器,利⽤测试⼯具进⾏并发请求测试,分析测试数据得到可以⽀撑并 发数量的评估,这个可以作为⼀个预警参考,俗话说知⼰⾃彼百战不殆。 第第三三⽅⽅服服务务:: 阿⾥云性能测试 并并发发测测试试⼯⼯具具:: Apache JMeter Visual Studio性能负载测试 Microsoft Web Application Stress Tool 实实战战⽅⽅案案 通通⽤⽤⽅⽅案案 ⽇⽤⼾流量⼤,但是⽐较分散,偶尔会有⽤⼾⾼聚的情况; 场景:⽤⼾签到,⽤⼾中⼼,⽤⼾订单,等 服务器架构图: 说明: 场景中的这些业务基本是⽤⼾进⼊APP后会操作到的,除了活动⽇(618,双11,等),这些业务的⽤⼾量都不会⾼聚集,同时 这些业务相关的表都是⼤数据表,业务多是查询操作,所以我们需要减少⽤⼾直接命中DB的查询;优先查询缓存,如果缓存 不存在,再进⾏DB查询,将查询结果缓存起来。
更新⽤⼾相关缓存需要分布式存储,⽐如使⽤⽤⼾ID进⾏hash分组,把⽤⼾分布到不同的缓存中,这样⼀个缓存集合的总 量不会很⼤,不会影响查询效率。 ⽅案如:⽤⼾签到获取积分 计算出⽤⼾分布的key,redis hash中查找⽤⼾今⽇签到信息 如果查询到签到信息,返回签到信息 如果没有查询到,DB查询今⽇是否签到过,如果有签到过,就把签到信息同步redis缓存。 如果DB中也没有查询到今⽇的签到记录,就进⾏签到逻辑,操作DB添加今⽇签到记录,添加签到积分(这整个DB 操作是⼀个事务) 缓存签到信息到redis,返回签到信息 注意这⾥会有并发情况下的逻辑问题,如:⼀天签到多次,发放多次积分给⽤⼾。 ⽤⼾订单 这⾥我们只缓存⽤⼾第⼀⻚的订单信息,⼀⻚40条数据,⽤⼾⼀般也只会看第⼀⻚的订单数据 ⽤⼾访问订单列表,如果是第⼀⻚读缓存,如果不是读DB 计算出⽤⼾分布的key,redis hash中查找⽤⼾订单信息 如果查询到⽤⼾订单信息,返回订单信息 如果不存在就进⾏DB查询第⼀⻚的订单数据,然后缓存redis,返回订单信息 ⽤⼾中⼼ 计算出⽤⼾分布的key,redis hash中查找⽤⼾订单信息 如果查询到⽤⼾信息,返回⽤⼾信息 如果不存在进⾏⽤⼾DB查询,然后缓存redis,返回⽤⼾信息 其他业务 上⾯例⼦多是针对⽤⼾存储缓存,如果是公⽤的缓存数据需要注意⼀些问题,如下 注意公⽤的缓存数据需要考虑并发下的可能会导致⼤量命中DB查询,可以使⽤管理后台更新缓存,或者DB查询 的锁住操作。 博⽂《⼤话Redis进阶》对更新缓存问题和推荐⽅案的分享。 http://blog.thankbabe.com/2016/08/05/redis-up/ 以上例⼦是⼀个相对简单的⾼并发架构,并发量不是很⾼的情况可以很好的⽀撑,但是随着业务的壮⼤,⽤⼾并发量增加, 我们的架构也会进⾏不断的优化和演变,⽐如对业务进⾏服务化,每个服务有⾃⼰的并发架构,⾃⼰的均衡服务器,分布式 数据库,nosql主从集群,如:⽤⼾服务、订单服务; 消消息息队队列列 秒杀、秒抢等活动业务,⽤⼾在瞬间涌⼊产⽣⾼并发请求 场景:定时领取红包,等
服务器架构图: 说明: 场景中的定时领取是⼀个⾼并发的业务,像秒杀活动⽤⼾会在到点的时间涌⼊,DB瞬间就接受到⼀记暴击,hold不住就会 宕机,然后影响整个业务; 像这种不是只有查询的操作并且会有⾼并发的插⼊或者更新数据的业务,前⾯提到的通⽤⽅案就⽆法⽀撑,并发的时候都 是直接命中DB; 设计这块业务的时候就会使⽤消息队列的,可以将参与⽤⼾的信息添加到消息队列中,然后再写个多线程程序去消耗队列, 给队列中的⽤⼾发放红包; ⽅案如:定时领取红包 ⼀般习惯使⽤ redis的 list 当⽤⼾参与活动,将⽤⼾参与信息push到队列中 然后写个多线程程序去pop数据,进⾏发放红包的业务 这样可以⽀持⾼并发下的⽤⼾可以正常的参与活动,并且避免数据库服务器宕机的危险 附加: 通过消息队列可以做很多的服务。 Tips:关注微信公众号:Java后端,每⽇提送技术博⽂。 如:定时短信发送服务,使⽤sset(sorted set),发送时间戳作为排序依据,短信数据队列根据时间升序,然后写个程序定时 循环去读取sset队列中的第⼀条,当前时间是否超过发送时间,如果超过就进⾏短信发送。 ⼀⼀级级缓缓存存 ⾼并发请求连接缓存服务器超出服务器能够接收的请求连接量,部分⽤⼾出现建⽴连接超时⽆法读取到数据的问题; 因此需要有个⽅案当⾼并发时候时候可以减少命中缓存服务器; 这时候就出现了⼀级缓存的⽅案,⼀级缓存就是使⽤站点服务器缓存去存储数据,注意只存储部分请求量⼤的数据,并且缓 存的数据量要控制,不能过分的使⽤站点服务器的内存⽽影响了站点应⽤程序的正常运⾏,⼀级缓存需要设置秒单位的过
期时间,具体时间根据业务场景设定,⽬的是当有⾼并发请求的时候可以让数据的获取命中到⼀级缓存,⽽不⽤连接缓存 nosql数据服务器,减少nosql数据服务器的压⼒ ⽐如APP⾸屏商品数据接⼝,这些数据是公共的不会针对⽤⼾⾃定义,⽽且这些数据不会频繁的更新,像这种接⼝的请求量 ⽐较⼤就可以加⼊⼀级缓存; 服务器架构图: 合理的规范和使⽤nosql缓存数据库,根据业务拆分缓存数据库的集群,这样基本可以很好⽀持业务,⼀级缓存毕竟是使⽤站点 服务器缓存所以还是要善⽤。 静静态态化化数数据据 ⾼并发请求数据不变化的情况下如果可以不请求⾃⼰的服务器获取数据那就可以减少服务器的资源压⼒。 对于更新频繁度不⾼,并且数据允许短时间内的延迟,可以通过数据静态化成JSON,XML,HTML等数据⽂件上传CDN,在拉取 数据的时候优先到CDN拉取,如果没有获取到数据再从缓存,数据库中获取,当管理⼈员操作后台编辑数据再重新⽣成静态⽂件 上传同步到CDN,这样在⾼并发的时候可以使数据的获取命中在CDN服务器上。 CDN节点同步有⼀定的延迟性,所以找⼀个靠谱的CDN服务器商也很重要 其其他他⽅⽅案案对于更新频繁度不⾼的数据,APP,PC浏览器,可以缓存数据到本地,然后每次请求接⼝的时候上传当前缓存数据的版本 号,服务端接收到版本号判断版本号与最新数据版本号是否⼀致,如果不⼀样就进⾏最新数据的查询并返回最新数据和最新 版本号,如果⼀样就返回状态码告知数据已经是最新。减少服务器压⼒:资源、带宽等. 分分层层,,分分割割,,分分布布式式 ⼤型⽹站要很好⽀撑⾼并发,这是需要⻓期的规划设计 在初期就需要把系统进⾏分层,在发展过程中把核⼼业务进⾏拆分成模块单元,根据需求进⾏分布式部署,可以进⾏独⽴团队维 护开发。分层将系统在横向维度上切分成⼏个部分,每个部⻔负责⼀部分相对简单并⽐较单⼀的职责,然后通过上层对下层的依赖和 调度组成⼀个完整的系统 ⽐如把电商系统分成:应⽤层,服务层,数据层。(具体分多少个层次根据⾃⼰的业务场景)
应⽤层:⽹站⾸⻚,⽤⼾中⼼,商品中⼼,购物⻋,红包业务,活动中⼼等,负责具体业务和视图展⽰ 服务层:订单服务,⽤⼾管理服务,红包服务,商品服务等,为应⽤层提供服务⽀持 数据层:关系数据库,nosql数据库 等,提供数据存储查询服务 分层架构是逻辑上的,在物理部署上可以部署在同⼀台物理机器上,但是随着⽹站业务的发展,必然需要对已经分层的 模块分离部署,分别部署在不同的服务器上,使⽹站可以⽀撑更多⽤⼾访问 分割在纵向⽅⾯对业务进⾏切分,将⼀块相对复杂的业务分割成不同的模块单元 包装成⾼内聚低耦合的模块不仅有助于软件的开发维护,也便于不同模块的分布式部署,提⾼⽹站的并发处理能⼒和功 能扩展 ⽐如⽤⼾中⼼可以分割成:账⼾信息模块,订单模块,充值模块,提现模块,优惠券模块等 分布式分布式应⽤和服务,将分层或者分割后的业务分布式部署,独⽴的应⽤服务器,数据库,缓存服务器 当业务达到⼀定⽤⼾量的时候,再进⾏服务器均衡负载,数据库,缓存主从集群 分布式静态资源,⽐如:静态资源上传cdn 分布式计算,⽐如:使⽤hadoop进⾏⼤数据的分布式计算 分布式数据和存储,⽐如:各分布节点根据哈希算法或其他算法分散存储数据 ⽹站分层-图1来⾃⽹络 集集群群对于⽤⼾访问集中的业务独⽴部署服务器,应⽤服务器,数据库,nosql数据库。核⼼业务基本上需要搭建集群,即多台服务器 部署相同的应⽤构成⼀个集群,通过负载均衡设备共同对外提供服务,服务器集群能够为相同的服务提供更多的并发⽀持,因此 当有更多的⽤⼾访问时,只需要向集群中加⼊新的机器即可, 另外可以实现当其中的某台服务器发⽣故障时,可以通过负载均衡的 失效转移机制将请求转移⾄集群中其他的服务器上,因此可以提⾼系统的可⽤性 应⽤服务器集群 nginx 反向代理
slb… … (关系/nosql)数据库集群 主从分离,从库集群 异异步步在⾼并发业务中如果涉及到数据库操作,主要压⼒都是在数据库服务器上⾯,虽然使⽤主从分离,但是数据库操作都是在主库上 操作,单台数据库服务器连接池允许的最⼤连接数量是有限的 当连接数量达到最⼤值的时候,其他需要连接数据操作的请求就需要等待有空闲的连接,这样⾼并发的时候很多请求就会出现 connection time out 的情况 那么像这种⾼并发业务我们要如何设计开发⽅案可以降低数据库服务器的压⼒呢? 如:⾃动弹窗签到,双11跨0点的时候并发请求签到接⼝ 双11抢红包活动 双11订单⼊库 等 设计考虑: 逆向思维,压⼒在数据库,那业务接⼝就不进⾏数据库操作不就没压⼒了 数据持久化是否允许延迟? 如何让业务接⼝不直接操作DB,⼜可以让数据持久化? ⽅案设计: 像这种涉及数据库操作的⾼并发的业务,就要考虑使⽤异步了 客⼾端发起接⼝请求,服务端快速响应,客⼾端展⽰结果给⽤⼾,数据库操作通过异步同步 如何实现异步同步? 使⽤消息队列,将⼊库的内容enqueue到消息队列中,业务接⼝快速响应给⽤⼾结果(可以温馨提⽰⾼峰期延迟到账) 然后再写个独⽴程序从消息队列dequeue数据出来进⾏⼊库操作,⼊库成功后刷新⽤⼾相关缓存,如果⼊库失败记录 ⽇志,⽅便反馈查询和重新持久化 这样⼀来数据库操作就只有⼀个程序(多线程)来完成,不会给数据带来压⼒ 补充:消息队列除了可以⽤在⾼并发业务,其他只要有相同需求的业务也是可以使⽤,如:短信发送中间件等 ⾼并发下异步持久化数据可能会影响⽤⼾的体验,可以通过可配置的⽅式,或者⾃动化监控资源消耗来切换时时或者使 ⽤异步,这样在正常流量的情况下可以使⽤时时操作数据库来提⾼⽤⼾体验 异步同时也可以指编程上的异步函数,异步线程,在有的时候可以使⽤异步操作,把不需要等待结果的操作放到异步 中,然后继续后⾯的操作,节省了等待的这部分操作的时间
缓缓存存⾼并发业务接⼝多数都是进⾏业务数据的查询,如:商品列表,商品信息,⽤⼾信息,红包信息等,这些数据都是不会经常变 化,并且持久化在数据库中 ⾼并发的情况下直接连接从库做查询操作,多台从库服务器也抗不住这么⼤量的连接请求数(前⾯说过,单台数据库服务器允许 的最⼤连接数量是有限的) 那么我们在这种⾼并发的业务接⼝要如何设计呢? 设计考虑: 还是逆向思维,压⼒在数据库,那么我们就不进⾏数据库查询 数据不经常变化,我们为啥要⼀直查询DB? 数据不变化客⼾端为啥要向服务器请求返回⼀样的数据? ⽅案设计: 数据不经常变化,我们可以把数据进⾏缓存,缓存的⽅式有很多种,⼀般的:应⽤服务器直接Cache内存,主流的:存 储在memcache、redis内存数据库 Cache是直接存储在应⽤服务器中,读取速度快,内存数据库服务器允许连接数可以⽀撑到很⼤,⽽且数据存储在内 存,读取速度快,再加上主从集群,可以⽀撑很⼤的并发查询 根据业务情景,使⽤配合客⼾端本地存,如果我们数据内容不经常变化,为啥要⼀直请求服务器获取相同数据,可以通 过匹配数据版本号,如果版本号不⼀样接⼝重新查询缓存返回数据和版本号,如果⼀样则不查询数据直接响应 这样不仅可以提⾼接⼝响应速度,也可以节约服务器带宽,虽然有些服务器带宽是按流量计费,但是也不是绝对⽆限 的,在⾼并发的时候服务器带宽也可能导致请求响应慢的问题 补充:缓存同时也指静态资源客⼾端缓存 cdn缓存,静态资源通过上传cdn,cdn节点缓存我们的静态资源,减少服务器压⼒
⾯⾯向向服服务务SOA⾯向服务架构设计 微服务更细粒度服务化,⼀系列的独⽴的服务共同组成系统 使⽤服务化思维,将核⼼业务或者通⽤的业务功能抽离成服务独⽴部署,对外提供接⼝的⽅式提供功能。 最理想化的设计是可以把⼀个复杂的系统抽离成多个服务,共同组成系统的业务,优点:松耦合,⾼可⽤性,⾼伸缩性,易维 护。通过⾯向服务化设计,独⽴服务器部署,均衡负载,数据库集群,可以让服务⽀撑更⾼的并发 服务例⼦: ⽤⼾⾏为跟踪记录统计 说明:通过上报应⽤模块,操作事件,事件对象,等数据,记录⽤⼾的操作⾏为 ⽐如:记录⽤⼾在某个商品模块,点击了某⼀件商品,或者浏览了某⼀件商品 背景:由于服务需要记录⽤⼾的各种操作⾏为,并且可以重复上报,准备接⼊服务的业务⼜是核⼼业务的⽤⼾⾏为跟踪,所以 请求量很⼤,⾼峰期会产⽣⼤量并发请求。 架构:nodejs WEB应⽤服务器均衡负载 redis主从集群 mysql主 nodejs+express+ejs+redis+mysql 服务端采⽤nodejs,nodejs是单进程(PM2根据cpu核数开启多个⼯作进程),采⽤事件驱动机制,适合I/O密集型业 务,处理⾼并发能⼒强 业务设计: 并发量⼤,所以不能直接⼊库,采⽤:异步同步数据,消息队列 请求接⼝上报数据,接⼝将上报数据push到redis的list队列中 nodejs写⼊库脚本,循环pop redis list数据,将数据存储⼊库,并进⾏相关统计Update,⽆数据时sleep⼏秒 因为数据量会⽐较⼤,上报的数据表按天命名存储 接⼝:上报数据接⼝ 统计查询接⼝ 上线跟进:
服务业务基本正常 每天的上报表有上千万的数据 冗冗余余,,⾃⾃动动化化 当⾼并发业务所在的服务器出现宕机的时候,需要有备⽤服务器进⾏快速的替代,在应⽤服务器压⼒⼤的时候可以快速添加机器 到集群中,所以我们就需要有备⽤机器可以随时待命。最理想的⽅式是可以通过⾃动化监控服务器资源消耗来进⾏报警,⾃动切 换降级⽅案,⾃动的进⾏服务器替换和添加操作等,通过⾃动化可以减少⼈⼯的操作的成本,⽽且可以快速操作,避免⼈为操作 上⾯的失误。 冗余数据库备份 备⽤服务器 ⾃动化⾃动化监控 ⾃动化报警 ⾃动化降级 通过GitLab事件,我们应该反思,做了备份数据并不代表就万⽆⼀失了,我们需要保证⾼可⽤性,⾸先备份是否正常进⾏,备份 数据是否可⽤,需要我们进⾏定期的检查,或者⾃动化监控,还有包括如何避免⼈为上的操作失误问题。(不过事件中gitlab的开 放性姿态,积极的处理⽅式还是值得学习的) 总总结结⾼并发架构是⼀个不断衍变的过程,冰洞三尺⾮⼀⽇之寒,⻓城筑成⾮⼀⽇之功 。打好基础架构⽅便以后的拓展,这点很重要。 【END】 如果看到这⾥,说明你喜欢这篇⽂章,请转转发发、、点点赞赞。微信搜索「web_resource」,关注后回复「进群」或者扫描下⽅⼆维码 即可进⼊⽆⼴告交流群。 ↓扫扫描描⼆⼆维维码码进进群群↓↓
声明:pdf仅供学习使⽤,⼀切版权归原创公众号所有;建议持续关注原创公众号获取最新⽂章,学习愉快! 推荐阅读 11… 前后端分离开发,RESTful 接⼝如何设计 22… ⾯试官:讲⼀下 Mybatis 初始化原理 33… 我们再来聊⼀聊 Java 的单例吧 44… 我采访了⼀位 Pornhub ⼯程师 55… 团队开发中 Git 最佳实践 喜欢⽂章,点个在在看看
⽀付系统⾼可⽤架构设计实战 冯忠旗 Java后端 2019-10-09 点击上⽅ Java后端,选择设为星标 优质⽂章,及时送达 来源 | 付钱拉 订阅号(id:fuqianla) 作者 | 冯忠旗 ⼀⼀、、背背景景 对于互联⽹应⽤和企业⼤型应⽤⽽⾔,多数都尽可能地要求做到724⼩时不间断运⾏,⽽要做到完全不间断运⾏可以说“难于上 ⻘天”。为此,对应⽤可⽤性程度的衡量标准⼀般有3个9到5个9。 可可⽤⽤性性指指标标 计计算算⽅⽅式式 不不可可⽤⽤时时间间((分分钟钟)) 99.9% 0.1%3652460 525.6 99.99% 0.01%3652460 52.56 99.999% 0.001%3652460 5.256 对于⼀个功能和数据量不断增加的应⽤,要保持⽐较⾼的可⽤性并⾮易事。为了实现⾼可⽤,「付钱拉」从避免单点故障、保证 应⽤⾃⾝的⾼可⽤、解决交易量增⻓等⽅⾯做了许多探索和实践。 在不考虑外部依赖系统突发故障,如⽹络问题、三⽅⽀付和银⾏的⼤⾯积不可⽤等情况下,「付钱拉」的服务能⼒可以达到 99.999%。 本⽂重点讨论如何提⾼应⽤⾃⾝的可⽤性,关于如何避免单点故障和解决交易量增⻓问题会在其他系列讨论。 为了提⾼应⽤的可⽤性,⾸先要做的就是尽可能避免应⽤出现故障,但要完全做到不出故障是不可能的。互联⽹是个容易产 ⽣“蝴蝶效应”的地⽅,任何⼀个看似很⼩的、发⽣概率为0的事故都可能出现,然后被⽆限放⼤。 ⼤家都知道RabbitMQ本⾝是⾮常稳定可靠的,「付钱拉」最开始也⼀直在使⽤单点RabbitMQ,并且从未出现运⾏故障,所以⼤ 家在⼼理上都认为这个东西不太可能出问题。 直到某天,这台节点所在的物理主机硬件因为年久失修坏掉了,当时这台RabbitMQ就⽆法提供服务,导致系统服务瞬间不可 ⽤。故障发⽣了也不可怕,最重要的是及时发现并解决故障。「付钱拉」对⾃⾝系统的要求是,秒级发现故障,快速诊断和解决故 障,从⽽降低故障带来的负⾯影响。 ⼆⼆、、问问题题 以史为鉴。⾸先我们简单的回顾⼀下,「付钱拉」曾经碰到的⼀些问题: (1) 新来的开发同事在处理新接⼊第三⽅通道时,由于经验不⾜忽视了设置超时时间的重要性。就是这样⼀个⼩⼩的细节,导致这 个三⽅队列所在的交易全部堵塞,同时影响到其他通道的交易; (2) 「付钱拉」系统是分布式部署的,并且⽀持灰度发布,所以环境和部署模块⾮常多⽽且复杂。某次增加了⼀个新模块,由于存 在多个环境,且每个环境都是双节点,新模块上线后导致数据库的连接数不够⽤,从⽽影响其他模块功能;
(3) 同样是超时问题,⼀个三⽅的超时,导致耗尽了当前所配置的所有worker threads, 以⾄于其他交易没有可处理的线程; (4) A三⽅同时提供鉴权,⽀付等接⼝,其中⼀个接⼝因为「付钱拉」交易量突增,从⽽触发A三⽅在⽹络运营商那边的DDoS限 制。通常机房的出⼝IP都是固定的,从⽽被⽹络运营商误认为是来⾃这个出⼝IP的交易是流量攻击,最终导致A三⽅鉴权和⽀付接 ⼝同时不可⽤。 (5) 再说⼀个数据库的问题,同样是因为「付钱拉」交易量突增引发的。建⽴序列的同事给某个序列的上限是999,999,999, 但数据库存的这个字段⻓度是32位,当交易量⼩的时候,系统产⽣的值和字段32位是匹配的,序列不会升位。可是随着交易量的 增加,序列不知不觉的升位数了,结果导致32位就不够存放。 类似这样的问题对于互联⽹系统⾮常常⻅,并且具有隐蔽性,所以如何避免就显得⾮常重要了。 三三、、解解决决⽅⽅案案 下⾯我们从三个⽅⾯来看「付钱拉」所做的改变。 3.1 尽可能避免故障 3.1.1 设计可容错的系统 ⽐如重路由,对于⽤⼾⽀付来说,⽤⼾并不关⼼⾃⼰的钱具体是从哪个通道⽀付出去的,⽤⼾只关⼼成功与否。「付钱拉」连接 30多个通道,有可能A通道⽀付不成功,这个时候就需要动态重路由到B或者C通道,这样就可以通过系统重路由避免⽤⼾⽀付失 败,实现⽀付容错。 还有针对OOM做容错,像Tomcat⼀样。系统内存总有发⽣⽤尽的情况,如果⼀开始就对应⽤本⾝预留⼀些内存,当系统发⽣ OOM的时候,就可以catch住这个异常,从⽽避免这次OOM。 3.1.2 某些环节快速失败“fail fast原则” Fail fast原则是当主流程的任何⼀步出现问题的时候,应该快速合理地结束整个流程,⽽不是等到出现负⾯影响才处理。 举个⼏个例⼦: (1)「付钱拉」启动的时候需要加载⼀些队列信息和配置信息到缓存,如果加载失败或者队列配置不正确,会造成请求处理过程的 失败,对此最佳的处理⽅式是加载数据失败,JVM直接退出,避免后续启动不可⽤;
(2)「付钱拉」的实时类交易处理响应时间最⻓是40s,如果超过40s前置系统就不再等待,释放线程,告知商⼾正在处理中,后续 有处理结果会以通知的⽅式或者业务线主动查询的⽅式得到结果; (3)「付钱拉」使⽤了redis做缓存数据库,⽤到的地⽅有实时报警埋点和验重等功能。如果连接redis超过50ms,那么这笔redis 操作会⾃动放弃,在最坏的情况下这个操作带给⽀付的影响也就是50ms,控制在系统允许的范围内。 3.1.3 设计具备⾃我保护能⼒的系统 系统⼀般都有第三⽅依赖,⽐如数据库,三⽅接⼝等。系统开发的时候,需要对第三⽅保持怀疑,避免第三⽅出现问题时候的连 锁反应,导致宕机。 (1)拆分消息队列 「付钱拉」提供各种各样的⽀付接⼝给商⼾,常⽤的就有快捷,个⼈⽹银,企业⽹银,退款,撤销,批量代付,批量代扣,单笔 代付,单笔代扣,语⾳⽀付,余额查询,⾝份证鉴权,银⾏卡鉴权,卡密鉴权等。与其对应的⽀付通道有微信⽀付,ApplePay, ⽀付宝等30多家⽀付通道,并且接⼊了⼏百家商⼾。在这三个维度下,如何确保不同业务、三⽅、商⼾、以及⽀付类型互不影 响,「付钱拉」所做的就是拆分消息队列。下图是部分业务消息队列拆分图: (2)限制资源的使⽤ 对于资源使⽤的限制设计是⾼可⽤系统最重要的⼀点,也是容易被忽略的⼀点,资源相对有限,⽤的过多了,⾃然会导致应⽤宕 机。为此「付钱拉」做了以下功课: 限制连接数 随着分布式的横向扩展,需要考虑数据库连接数,⽽不是⽆休⽌的最⼤化。数据库的连接数是有限制的,需要全局考量所有的模 块,特别是横向扩展带来的增加。 限制内存的使⽤ 内存使⽤过⼤,会导致频繁的GC和OOM,内存的使⽤主要来⾃以下两个⽅⾯: A:集合容量过⼤;
B:未释放已经不再引⽤的对象,⽐如放⼊ThreadLocal的对象⼀直会等到线程退出的时候回收。 限制线程创建 线程的⽆限制创建,最终导致其不可控,特别是隐藏在代码中的创建线程⽅法。 当系统的SY值过⾼时,表⽰Linux需要花费更多的时间进⾏线程切换。Java造成这种现象的主要原因是创建的线程⽐较多,且这 些线程都处于不断的阻塞(锁等待,IO等待)和执⾏状态的变化过程中,这就产⽣了⼤量的上下⽂切换。 除此之外,Java应⽤在创建线程时会操作JVM堆外的物理内存,太多的线程也会使⽤过多的物理内存。 对于线程的创建,最好通过线程池来实现,避免线程过多产⽣上下⽂切换。 限制并发 做过⽀付系统的应该清楚,部分三⽅⽀付公司是对商⼾的并发有要求的。三⽅给开放⼏个并发是根据实际交易量来评估的,所以 如果不控制并发,所有的交易都发给三⽅,那么三⽅只会回复“请降低提交频率”。 所以在系统设计阶段和代码review阶段都需要特别注意,将并发限制在三⽅允许的范围内。 我们讲到「付钱拉」为z实现系统的可⽤性做了三点改变,其⼀是尽可能避免故障,接下来讲后⾯两点。 3.2 及时发现故障 故障就像⻤⼦进村,来的猝不及防。当预防的防线被冲破,如何及时拉起第⼆道防线,发现故障保证可⽤性,这时候报警监控系 统的开始发挥作⽤了。⼀辆没有仪表盘的汽⻋,是⽆法知道⻋速和油量,转向灯是否亮,就算“⽼司机”⽔平再⾼也是相当危险 的。同样,系统也是需要监控的,最好是出现危险的时候提前报警,这样可以在故障真正引发⻛险前解决。 3.2.1 实时报警系统 如果没有实时报警,系统运⾏状态的不确定性会造成⽆法量化的灾难。「付钱拉」的监控系统指标如下: 实时性-实现秒级监控; 全⾯性-覆盖所有系统业务,确保⽆死⻆覆盖; 实⽤性-预警分为多个级别,监控⼈员可以⽅便实⽤地根据预警严重程度做出精确的决策; 多样性-预警⽅式提供推拉模式,包括短信,邮件,可视化界⾯,⽅便监控⼈员及时发现问题。 报警主要分为单机报警和集群报警,⽽「付钱拉」属于集群部署。实时预警主要依靠各个业务系统实时埋点数据统计分析实现, 因此难度主要在数据埋点和分析系统上。 3.2.2 埋点数据 要做到实时分析,⼜不影响交易系统的响应时间,「付钱拉」在系统各个模块中通过redis实时做数据埋点,然后将埋点数据汇总 到分析系统,分析系统根据规则进⾏分析报警。
3.2.3 分析系统 分析系统最难做的是业务报警点,例如哪些报警只要⼀出来就必须出警,哪些报警⼀出来只需要关注。下⾯我们对分析系统做⼀ 个详细介绍: (1)系统运⾏架构 (2)系统运⾏流程 (3)系统业务监控点 「付钱拉」的业务监控点都是在⽇常运⾏过程中⼀点⼀滴总结出来的,分为出警类和关注类两⼤块。 A:出警类⽹络异常预警; 单笔订单超时未完成预警; 实时交易成功率预警; 异常状态预警; 未回盘预警; 失败通知预警;
异常失败预警; 响应码频发预警; 核对不⼀致预警; 特殊状态预警; B:关注类交易量异常预警; 交易额超过500W预警; 短信回填超时预警; ⾮法IP预警; 3.2.4 ⾮业务监控点 ⾮业务监控点主要是指从运维⻆度的监控,包括⽹络,主机,存储,⽇志等。具体如下: (1)服务可⽤性监控 使⽤JVM采集Young GC/Full GC次数及时间、堆内存、耗时Top 10线程堆栈等信息,包括缓存buffer的⻓度。 (2)流量监控 通过Agent监控代理部署在各个服务器上,实时采集流量情况。 (3)外部系统监控 通过间隙性探测来观察三⽅或者⽹络是否稳定。 (4)中间件监控 针对MQ消费队列,通过RabbitMQ脚本探测,实时分析队列深度; 针对数据库部分,通过安装插件xdb,实时监控数据库性能。 (5)实时⽇志监控 通过rsyslog完成分布式⽇志的归集,然后通过系统分析处理,完成⽇志实时监控和分析。最后,通过开发可视化⻚⾯展⽰给使⽤ 者。(6)系统资源监控 通过Zabbix监控主机的CPU负载、内存使⽤率、各⽹卡的上下⾏流量、各磁盘读写速率、各磁盘读写次数(IOPS)、各磁盘空间使 ⽤率等。 以上就是「付钱拉」实时监控系统所做的,主要分为业务点监控和运维监控两⽅⾯,虽然系统是分布式部署,但是每个预警点都
是秒级响应。除此之外,业务系统的报警点也有⼀个难点,那就是有些报警是少量报出来不⼀定有问题,⼤量报警就会有问题, 也就是所谓的量变引起质变。 举⼀个例⼦,拿⽹络异常来说,发⽣⼀笔可能是⽹络抖动,但是多笔发⽣就需要重视⽹络是否真的有问题,针对⽹络异常「付钱 拉」的报警样例如下: 单通道⽹络异常预警:1分钟内A通道⽹络异常连续发⽣了12笔,触发了预警阀值; 多通道⽹络异常预警1: 10分钟内,连续每分钟内⽹络异常发⽣了3笔,涉及3个通道,触发了预警阀值; 多通道⽹络异常预警2:10分钟内,总共发⽣⽹络异常25笔,涉及3个通道, 触发了预警阀值. 3.2.5 ⽇志记录和分析系统 对于⼀个⼤型系统⽽⾔,每天记录⼤量的⽇志和分析⽇志是有⼀定的难度的。「付钱拉」每天平均有200W笔订单量,⼀笔交易 经过⼗⼏个模块流转,假设⼀笔订单记录30条⽇志,可想⽽知每天会有多么巨⼤的⽇志量。 「付钱拉」⽇志的分析有两个作⽤,⼀个是实时⽇志异常预警,另外⼀个是提供订单轨迹给运营⼈员使⽤。 (1)实时⽇志预警 实时⽇志预警是针对所有实时交易⽇志,实时抓取带有Exception或者Error的关键字然后报警。这样的好处是,如果代码中有任 何运⾏异常,都会第⼀时间发现。「付钱拉」针对实时⽇志预警的处理⽅式是,⾸先采⽤rsyslog完成⽇志归集,然后通过分析系 统实时抓取,再做实时预警。 (2)订单轨迹 对于交易系统,⾮常有必要实时了解⼀笔订单的状态流转。「付钱拉」最初的做法是通过数据库来记录订单轨迹,但是运⾏⼀段 时间后,发现订单量剧增导致数据库表过⼤不利于维护。 「付钱拉」现在的做法是,每个模块通过打印⽇志轨迹,⽇志轨迹打印的格式按照数据库表结构的⽅式打印,打印好所有⽇志 后,rsyslog来完成⽇志归集,分析系统会实时抓取打印的规范⽇志,进⾏解析然后按天存放到数据库中,并展⽰给运营⼈员可视 化界⾯。 ⽇志打印规范如下: 2016-07-22 18:15:00.512||pool-73-thread-4||通道适配器||通道适配器-发三⽅ 后||CEX16XXXXXXX5751||16201XXXX337||||||04||9000||【结算平台消息】处理中||0000105||98XX543210||GHT||03||11||2016- 07-22 18:15:00.512||张张||||01||tunnelQuery||true||||Pending||||10.100.140.101||8cff785d-0d01-4ed4-b771- cb0b1faa7f95||10.999.140.101||O001||||0.01||||||||http://10.100.444.59:8080/regression/notice||||240||2016-07-20 19:06:13.000xxxxxxx ||2016-07-22 18:15:00.170||2016-07-22 18:15:00.496xxxxxxxxxxxxxxxxxxxx ||2016-07-2019:06:13.000||||||||01||0103||111xxxxxxxxxxxxxxxxxxxxxxxxx ||8fb64154bbea060afec5cd2bb0c36a752be734f3e9424ba7xxxxxxxxxxxxxxxxxxxx ||622xxxxxxxxxxxxxxxx||9bc195a59dd35a47||f2ba5254f9e22914824881c242d211 ||||||||||||||||||||6xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx010|||||||||| 简要⽇志可视化轨迹如下:
⽇志记录和分析系统除了以上两点,也提供了交易和响应报⽂的下载和查看。 33…22…66 77**2244⼩⼩时时监监控控室室 「付钱拉」以上的报警项⽬给操作⼈员提供推拉两种⽅式,⼀种是短信和邮件推送,⼀种是报表展⽰。除此之外,由于⽀付系统 相⽐互联⽹其他系统本⾝的重要性,「付钱拉」采⽤724⼩时的监控室保证系统的安全稳定。 33…33 及及时时处处理理故故障障 在故障发⽣之后,特别是⽣产环境,第⼀时间要做的不是寻找故障发⽣的原因,⽽是以最快速度处理故障,保障系统的可⽤性。 「付钱拉」常⻅的故障和处理措施如下: 33…33…11 ⾃⾃动动修修复复 针对⾃动修复部分,「付钱拉」常⻅的故障都是三⽅不稳定造成的,针对这种情况,就是上⾯说的系统会⾃动进⾏重路由。 33…33…22 服服务务降降级级 服务降级指在出现故障的情况下⼜⽆法快速修复的情况下,把某些功能关闭,以保证核⼼功能的使⽤。「付钱拉」针对商⼾促销 的时候,如果某个商⼾交易量过⼤,会实时的调整这个商⼾的流量,使此商⼾服务降级,从⽽不会影响到其他商⼾,类似这样的 场景还有很多,具体的服务降级功能会在后续系列介绍。 四四、、QQ&&AA Q1: 能讲讲当年那台RabbitMQ宕掉的具体细节和处理⽅案吗? A1: RabbitMQ宕机时间引发了对系统可⽤性的思考,当时我们的RabbitMQ本⾝并没有宕机(RabbitMQ还是很稳定的),宕机 的是RabbitMQ所在的硬件机器,但是问题就出在当时RabbiMQ的部署是单点部署,并且⼤家惯性思维认为RabbitMQ不会宕 机,从⽽忽略了它所在的容器,所以这个问题的产⽣对于我们的思考就是所有的业务不可以有单点,包括应⽤服务器、中间件、 ⽹络设备等。单点不仅仅需要从单点本⾝考虑,⽐如整个服务做双份,然后AB测试,当然也有双机房的。 Q2: 贵公司的开发运维是在⼀起的吗? A2: 我们开发运维是分开的,今天的分享主要是站在整个系统可⽤性层⾯来考虑的,开发偏多,有⼀部分运维的东西。这些付钱 拉的⾛过的路,是我⼀路⻅证过的。 Q3: 你们的后台全部使⽤的Java吗?有没有考虑其他语⾔? A3: 我们⽬前系统多数是Java,有少数的Python、PHP、C++,这个取决于业务类型,⽬前java这个阶段最适合我们,可能随着
业务的扩展,会考虑其他语⾔。 Q4: 对第三⽅依赖保持怀疑,能否举个具体的例⼦说明下怎么样做?万⼀第三⽅完全不能⽤了怎么办 A4: 系统⼀般都有第三⽅依赖,⽐如数据库,三⽅接⼝等。系统开发的时候,需要对第三⽅保持怀疑,避免第三⽅出现问题时候 的连锁反应,导致宕机。⼤家都知道系统⼀旦发⽣问题都是滚雪球的,越来越⼤。⽐如说我们扫码通道,如果只有⼀家扫码通 道,当这家扫码通道发⽣问题的时候是没有任何办法的,所以⼀开始就对它表⽰怀疑,通过接⼊多家通道,如果⼀旦发⽣异常, 实时监控系统触发报警后就⾃动进⾏路由通道切换,保证服务的可⽤性;其⼆,针对不同的⽀付类型、商⼾、交易类型做异步消 息拆分,确保如果⼀旦有⼀种类型的交易发⽣不可预估的异常后,从⽽不会影响到其他通道,这个就好⽐⾼速公路多⻋道⼀样, 快⻋和慢⻋道互不影响。其实总体思路就是容错+拆分+隔离,这个具体问题具体对待。 Q5: ⽀付超时后,会出现⽹络问题,会不会存在钱已付,订单丢失,如何做容灾及数据⼀致性,⼜有没重放⽇志,修过数据? A5:做⽀付最重要的就是安全,所以针对订单状态我们都是保守处理策略,因此对于⽹络异常的订单我们都是设置处理中状态, 然后最终通过主动查询或者被动接受通知来完成和银⾏或者三⽅的最终⼀致性。⽀付系统中,除了订单状态还有响应码问题,⼤ 家都知道银⾏或者三⽅都是通过响应码来响应的,响应码和订单状态的翻译也是⼀定要保守策略,确保不会出现资⾦多付少付等 问题。总之这个点的总体思路是,资⾦安全第⼀,所有的策略都是⽩名单原则。 Q6: 刚才提到过,若某⽀付通道超时,路由策略会分发⾄另⼀通道,根据那个通道图可看出,都是不同的⽀付⽅式,⽐如⽀付宝 或微信⽀付,那如果我只想通过微信⽀付,为啥不是重试,⽽要换到另⼀通道呢?还是通道本⾝意思是请求节点? A6:⾸先针对超时不可以做重路由,因为socket timeout是不能确定这笔交易是否发送到了三⽅,是否已经成功或者失败,如 果是成功了,再重试⼀遍如果成功,针对付款就是多付,这种情况的资⾦损失对公司来说不可以的;其次,针对路由功能,需要 分业务类型,如果是单笔代收付交易,⽤⼾是不关⼼钱是哪个通道出去的,是可以路由的,如果是扫码通道,⽤⼾如果⽤微信扫 码,肯定最终是⾛微信,但是我们有好多中间渠道,微信是通过中间渠道出去的,这⾥我们可以路由不同的中间渠道,这样最终 对于⽤⼾来说还是微信⽀付。 Q7: 能否举例说下⾃动修复的过程?如何发现不稳定到重路由的细节? A7: ⾃动修复也就是通过重路由做容错处理,这个问题⾮常好,如果发现不稳定然后去决策重路由。重路由⼀定是明确当前被重 路由的交易没有成功才可以路由,否则就会造成多付多收的资⾦问题。我们系统⽬前重路由主要是通过事后和事中两种⽅式来决 策的,针对事后⽐如5分钟之内通过实时预警系统发现某个通道不稳定,那么就会把当期之后的交易路由到别的通道;针对事中 的,主要是通过分析每笔订单返回的失败响应码,响应码做状态梳理,明确可以重发的才做重路由。这⾥我指列举这两点,其他 的业务点还⾮常多,鉴于篇幅原因,不做详述,但是总体思路是必须有⼀个内存实时分析系统,秒级决策,这个系统必须快,然 后结合实时分析和离线分析做决策⽀撑,我们的实时秒级预警系统就做这个事情。 Q8: 商⼾促销有规律吗?促销时峰值与平时相⽐会有多少差别?有技术演练么?降级的优先级是怎样的? A8:商⼾促销⼀般我们会事先经常和商⼾保持沟通,事先了解促销的时间点和促销量,然后针对性做⼀些事情;促销峰值和平时 差距⾮常⼤,促销⼀般都是2个⼩时之内的⽐较多,⽐如有的卖理财产品,促销也就集中在1个⼩时之内,所以峰值⾮常⾼;技术 演练是我们在了解商⼾的促销量,然后预估系统的处理能⼒,然后提前做演练;降级的优先级主要是针对商⼾的,由于接⼊我们 的商⼾⽀付场景⽐较多的,有理财,有代收付,有快捷,有扫码等等,所以我们整体原则就是不同的商⼾之间⼀定不可以相互影 响,因为不能因为你家做促销影响了其他商家。 Q9:rsyslog归集⽇志怎么存储的? A9: 这个是好问题,刚开始我们的⽇志也就是订单轨迹log是记录在数据库表中的,结果发现⼀笔订单流转需要好多模块,这样⼀ 笔订单的⽇志轨迹就是10笔左右,如果⼀天400w笔交易的话,这张数据库表就有问题了,就算拆分也是会影响数据库性能的, 并且这个属于辅助业务,不应该这样做。然后,我们发现写⽇志⽐写数据库好,所以把实时⽇志打印成表格的形式,打印到硬盘 上,这块由于只是实时⽇志所以⽇志量不⼤,就是在⽇志服务器的⼀个固定⽬录下。由于⽇志都是在分布式机器上,然后通过归 集⽇志到⼀个集中的地⽅,这块是通过挂载存储的,然后有专⻔运维团队写的程序去实时解析这些表格形式的⽇志,最终通过可 视化⻚⾯展⽰到运营操作⻚⾯,这样运营⼈员看到的订单轨迹⼏乎是实时的,您关⼼的怎么存储实际上不是啥问题,因为我们分 了实时⽇志和离线⽇志,然后超过⼀定时间的离线⽇志会切割,最终被删除。 Q10: 系统监控和性能监控如何配合的? A10:我理解的系统监控包括了系统性能监控,系统性能监控是系统整体监控的⼀部分,不存在配合问题,系统性能监控有多个 维度,⽐如应⽤层⾯,中间件,容器等。系统的⾮业务监控可以查看⽂章分享。
声明:pdf仅供学习使⽤,⼀切版权归原创公众号所有;建议持续关注原创公众号获取最新⽂章,学习愉快! -E N D- 如果看到这⾥,说明你喜欢这篇⽂章,请转转发发、、点点赞赞。微信搜索「web_resource」,关注后回复「进群」或者扫描下⽅⼆维码 即可进⼊⽆⼴告交流群。 ↓扫扫描描⼆⼆维维码码进进群群↓↓ 推荐阅读 11… Java后端优质⽂章整理 22… JDK 13 新特性⼀览 33… 14 个实⽤的数据库设计技巧 44… ⾯试官:Redis 内存满了怎么办? 55… 如何设计 API 接⼝,实现统⼀格式返回? 喜欢⽂章,点个在在看看
每⽇⼀题:如果让你写⼀个消息队列,该如何进⾏架构设计? Java后端 2019-10-07 点击上⽅Java后端,选择“设为星标” 优质⽂章,及时送达 原⽂来⾃ GitHub 开源社区 Doocs,欢迎 Star 此项⽬,如果你有独到的⻅解,同样可以参与贡献此项⽬。 ⾯⾯试试题题 如果让你写⼀个消息队列,该如何进⾏架构设计?说⼀下你的思路? ⾯⾯试试官官⼼⼼理理分分析析 其实聊到这个问题,⼀般⾯试官要考察两块: 1. 你有没有对某⼀个消息队列做过较为深⼊的原理的了解,或者从整体了解把握住⼀个消息队列的架构原理。 2. 看看你的设计能⼒,给你⼀个常⻅的系统,就是消息队列系统,看看你能不能从全局把握⼀下整体架构设计,给出⼀些关键点 出来。 说实话,问类似问题的时候,⼤部分⼈基本都会蒙,因为平时从来没有思考过类似的问题,⼤多数⼈就是平时埋头⽤,从来不去 思考背后的⼀些东西。类似的问题,⽐如,如果让你来设计⼀个 Spring 框架你会怎么做?如果让你来设计⼀个 Dubbo 框架你会 怎么做?如果让你来设计⼀个 MyBatis 框架你会怎么做? ⾯⾯试试题题剖剖析析 其实回答这类问题,说⽩了,不求你看过那技术的源码,起码你要⼤概知道那个技术的基本原理、核⼼组成部分、基本架构构 成,然后参照⼀些开源的技术把⼀个系统设计出来的思路说⼀下就好。 ⽐如说这个消息队列系统,我们从以下⼏个⻆度来考虑⼀下: ⾸先这个 mq 得⽀持可伸缩性吧,就是需要的时候快速扩容,就可以增加吞吐量和容量,那怎么搞?设计个分布式的系统 呗,参照⼀下 kafka 的设计理念,broker -> topic -> partition,每个 partition 放⼀个机器,就存⼀部分数据。如果现在 资源不够了,简单啊,给 topic 增加 partition,然后做数据迁移,增加机器,不就可以存放更多数据,提供更⾼的吞吐量 了?其次你得考虑⼀下这个 mq 的数据要不要落地磁盘吧?那肯定要了,落磁盘才能保证别进程挂了数据就丢了。那落磁盘的时 候怎么落啊?顺序写,这样就没有磁盘随机读写的寻址开销,磁盘顺序读写的性能是很⾼的,这就是 kafka 的思路。 其次你考虑⼀下你的 mq 的可⽤性啊?这个事⼉,具体参考之前可⽤性那个环节讲解的 kafka 的⾼可⽤保障机制。多副本 - > leader & follower -> broker 挂了重新选举 leader 即可对外服务。 能不能⽀持数据 0 丢失啊?可以的,参考我们之前说的那个 kafka 数据零丢失⽅案。 mq 肯定是很复杂的,⾯试官问你这个问题,其实是个开放题,他就是看看你有没有从架构⻆度整体构思和设计的思维以及能 ⼒。确实这个问题可以刷掉⼀⼤批⼈,因为⼤部分⼈平时不思考这些东西。 -END- 如果看到这⾥,说明你喜欢这篇⽂章,帮忙转转发发⼀下吧,感谢。微信搜索「web_resource」,关注后即可获取每⽇⼀题的推
阅读原⽂ 声明:pdf仅供学习使⽤,⼀切版权归原创公众号所有;建议持续关注原创公众号获取最新⽂章,学习愉快! 送。推荐阅读 11… 每⽇⼀题:消息队列⾯试常问题⽬ 22… 每⽇⼀题:如何保证消息队列的⾼可⽤? 33… 每⽇⼀题:如何设计⼀个⾼并发系统? 44… 每⽇⼀题:为什么要进⾏系统拆分? 55… 每⽇⼀题:你有没有做过 MySQL 读写分离? 66… 每⽇⼀题:如何保证消息不被重复消费? 77… ⾯试官:集群部署时,分布式 session 如何实现? 88… ⾯试题:InnoDB 中⼀棵 B+ 树能存多少⾏数据? 99… ⾯试官:Redis 内存满了怎么办? 喜欢⽂章,点个在在看看
每⽇⼀题:讲⼀讲你理解的微服务架构? Java后端 2019-10-27 点击上⽅ Java后端,选择 设为星标 优质⽂章,及时送达 原⽂来⾃ GitHub 开源社区 Doocs,欢迎 Star 此项⽬,如果你有独到的⻅解,同样可以参与贡献此项⽬。 ⾯⾯试试题题 讲⼀讲你理解的微服务架构? ⾯⾯试试题题剖剖析析 翻译⾃ Martin Fowler ⽹站 Microservices ⼀⽂。⽂章篇幅较⻓,阅读需要⼀点耐⼼。 本⼈⽔平有限,若有不妥之处,还请各位帮忙指正,谢谢。 过去⼏年中出现了“微服务架构”这⼀术语,它描述了将软件应⽤程序设计为若⼲个可独⽴部署的服务套件的特定⽅法。尽管这 种架构⻛格尚未有精确的定义,但围绕业务能⼒、⾃动部署、端点智能以及语⾔和数据的分散控制等组织来说,它们还是存在着 某些共同特征。 “微服务”——在拥挤的软件架构街道上⼜⼀个新名词。虽然我们的⾃然倾向是对它轻蔑⼀瞥,但这⼀术语描述了⼀种越来越具 有吸引⼒的软件系统⻛格。在过去⼏年中,我们已经看到许多项⽬使⽤了这种⻛格,到⽬前为⽌其结果都是正向的,以⾄于它变 成了我们 ThoughtWorks 许多同事构建企业应⽤程序的默认⻛格。然⽽遗憾的是,并没有太多信息可以概述微服务的⻛格以及如 何实现。 简⽽⾔之,微服务架构⻛格[1]是⼀种将单个应⽤程序开发为⼀套⼩型服务的⽅法,每个⼩型服务都在⾃⼰的进程中运⾏,并以轻 量级机制(通常是 HTTP 资源 API)进⾏通信。这些服务围绕业务功能构建,可通过全⾃动部署机制来独⽴部署。这些服务共⽤ ⼀个最⼩型的集中式管理,它们可以使⽤不同的编程语⾔编写,并使⽤不同的数据存储技术。 在开始解释微服务⻛格之前,将它与单体(monolithic)⻛格进⾏⽐较是有⽤的:单体应⽤程序被构建为单⼀单元。企业应⽤程 序通常由三个部分构成:客⼾端⽤⼾界⾯(由⽤⼾机器上的浏览器中运⾏的 HTML ⻚⾯和 Javascript 组成)、数据库(由许多表 组成,通常是在关系型数据库中管理)系统、服务器端应⽤程序。服务器端应⽤程序处理 HTTP 请求,执⾏⼀些逻辑处理,从数 据库检索和更新数据,选择数据并填充到要发送到浏览器的 HTML 视图中。这个服务器端应⽤程序是⼀个整体⸺⼀个逻辑可执 ⾏⽂件[2]。对系统的任何更改都涉及构建和部署新版本的服务器端应⽤程序。 这种单体服务器是构建这种系统的⾃然⽅式。处理⼀个请求的所有逻辑都在⼀个进程中运⾏,允许你使⽤语⾔的基本功能将应⽤ 程序划分为类、函数和命名空间。需要注意的是,你可以在开发⼈员的笔记本电脑上运⾏和测试应⽤程序,并使⽤部署管道确保 对程序做出的改动被适当测试并部署到⽣产环境中。你可以通过在负载均衡器后⾯运⾏许多实例来⽔平扩展整体块。 单体应⽤程序可以取得成功,但越来越多的⼈对它们感到不满——尤其是在将更多应⽤程序部署到云的时候。变更周期被捆绑在 ⼀起——即使只是对应⽤程序的⼀⼩部分进⾏了更改,也需要重建和部署整个单体应⽤。随着时间的推移,通常很难保持良好的 模块化结构,也更难以保持应该只影响该模块中的⼀个模块的更改。对系统进⾏扩展时,不得不扩展整个应⽤系统,⽽不能仅扩 展该系统中需要更多资源的那些部分。
这些不满催⽣了微服务架构⻛格:将应⽤程序构建为服务套件。除了服务可独⽴部署、独⽴扩展的事实之外,每个服务还提供了 ⼀个牢固的模块边界,甚⾄允许以不同的编程语⾔编写不同的服务。他们也可以由不同的团队管理。 我们并不认为微服务⻛格是新颖的或创新的,其根源⾄少可以追溯到 Unix 的设计原则。但我们认为没有⾜够多的⼈考虑微服务 架构,如果使⽤它,许多软件的开发会变得更好。 微服务架构的特征 虽然不能说微服务架构⻛格有正式的定义,但我们可以尝试描述⼀下我们认为的在符合这个标签的架构中,它们所具有的⼀些共 同特征。与概述共同特征的任何定义⼀样,并⾮所有微服务架构都具有所有特征,但我们确实期望⼤多数微服务架构都具有⼤多 数特征。虽然我们的作者⼀直是这个相当宽松的社区的活跃成员,但我们的本意还是尝试描述我们两⼈在⾃⼰和⾃⼰所了解的团 队的⼯作中所看到的情况。特别要说明的是,我们没有制定⼀些相关的定义。 通过服务进⾏组件化 只要我们参与软件⾏业,就⼀直希望通过将组件集成在⼀起来构建系统,就像我们在物理世界中看到的事物的构建⽅式⼀样。在 过去的⼏⼗年中,我们已经看到了⼤多数语⾔平台的公共软件库都取得了极⼤的进展。 在谈论组件时,就会碰到⼀个有关定义的难题,即什么是组件?我们的定义是,组件是可独⽴更换和升级的软件单元。 微服务架构也会使⽤软件库,但组件化软件的主要⽅式是拆分为多个服务。我们把库定义为链接到程序并使⽤内存函数调⽤来调 ⽤的组件,⽽服务是⼀种进程外组件,通过 Web 服务请求或远程过程调⽤等机制进⾏通信。(这与许多⾯向对象程序中的服务对 象的概念是不同的[3]。) 将服务作为组件(⽽不是库)的⼀个主要原因是服务可以独⽴部署。如果你有⼀个应⽤程序[4]是由单⼀进程⾥的多个库组成,任 何⼀个组件的更改都会导致整个应⽤程序的重新部署。但如果应⽤程序可拆分为多个服务,那么单个服务的变更只需要重新部署 该服务即可。当然这也不是绝对的,⼀些服务接⼝的修改可能会导致多个服务之间的协同修改,但⼀个好的微服务架构的⽬的是 通过内聚服务边界和服务协议的演进机制来最⼩化这些协同修改。 将服务⽤作组件的另⼀个结果是更明确的组件接⼝。⼤多数语⾔没有⼀个良好的机制来定义显式发布的接⼝。通常,它只是⽂档
和规则来阻⽌客⼾端破坏组件的封装,这会导致组件之间过于紧耦合。通过使⽤显式远程调⽤机制,服务可以更轻松地避免这种 情况。 像这样使⽤服务确实存在⼀些不好的地⽅。远程调⽤⽐进程内调⽤更昂贵,远程 API 需要设计成较粗的粒度,这通常更难以使 ⽤。如果你需要更改组件之间的职责分配,那么当你跨越进程边界时,这种组件⾏为的改动会更加难以实现。 近似地,我们可以把⼀个个服务映射为⼀个个运⾏时进程,但这仅仅是⼀个近似⽽已。⼀个服务可能包括多个始终⼀起开发和部 署的进程,⽐如⼀个应⽤系统的进程和仅由该服务使⽤的数据库。 围绕业务能⼒进⾏组织 在将⼤型应⽤程序拆分为多个部分时,管理层往往侧重于技术层⾯,从⽽导致 UI 团队、服务器端逻辑团队、数据库团队的划分。 当团队按照这些⽅式分开时,即便是简单的更改也可能导致跨团队项⽬的时间和预算批准。⼀个聪明的团队将围绕这个进⾏优 化,“两害相权取其轻”——只需将逻辑强制应⽤到他们可以访问的任何应⽤程序中。换句话说,逻辑⽆处不在。这是康威定律 [5]的⼀个例⼦。 任何设计系统(⼴义上的)的组织都会产⽣⼀种设计,其结构是组织通信结构的副本。—— 梅尔⽂•康威,1967年 微服务采⽤不同的划分⽅式,它是围绕业务功能将系统拆分为多个服务 。这些服务为该业务领域采⽤⼴泛的软件实现,包括⽤⼾ 界⾯、持久化存储和任何外部协作。因此,团队是跨职能的,包括开发所需的全部技能:⽤⼾体验、数据库和项⽬管理。
以这种⽅式组建的⼀家公司是 www.comparethemarket.com。跨职能团队负责构建和运营每个产品,每个产品拆分为多个独 ⽴的服务,彼此通过消息总线来通信。 ⼤型单体应⽤程序也可以围绕业务功能进⾏模块化,尽管这不是常⻅的情况。当然,我们会敦促构建单体应⽤系统的⼤型团队根 据业务线来将⾃⼰分解为若⼲⼩团队。我们在这⾥看到的主要问题是,它们往往围绕太多的上下⽂进⾏组织。如果单体跨越了模 块边界,对团队的个体成员来说,很难将它们装⼊短期的记忆中。此外,我们看到模块化⽣产线需要⼤量的规则来执⾏。服务组 件所要求的更加明确的分离,使得它更容易保持团队边界清晰。 是产品不是项⽬ 我们看到的⼤多数应⽤程序开发⼯作都使⽤这样⼀个项⽬模式:⽬标是交付⼀些软件,然后就完⼯了。⼀旦完成后,软件将移交 给维护组织,然后构建它的项⽬团队也随之解散了。 微服务⽀持者倾向于避免这种模式,⽽是认为团队应该负责产品的整个⽣命周期。对此⼀个共同的启⽰是亚⻢逊的“you build, you run it”的概念,开发团队对⽣产中的软件负全部责任。这使开发者经常接触他们的软件在⽣产环境如何⼯作,并增加与他 们的⽤⼾联系,因为他们必须承担⾄少部分的⽀持⼯作。 产品⼼态与业务能⼒的联系紧密相连。要持续关注软件如何帮助⽤⼾提升业务能⼒,⽽不是把软件看成是将要完成的⼀组功能。 没有理由说为什么这种⽅法不能⽤在单⼀应⽤程序上,但较⼩的服务粒度,使得它更容易在服务开发者和⽤⼾之间建⽴个⼈关 系。智能端点和哑管 在不同进程之间建⽴通信时,我们已经看到许多产品和⽅法,都强调将⼤量的智能特性放⼊通信机制本⾝。⼀个很好的例⼦是企 业服务总线(ESB),其中 ESB 产品通常包括⽤于消息路由、编排、转换和应⽤业务规则的复杂⼯具。 微服务社区倾向于采⽤另⼀种⽅法:智能端点和哑管。基于微服务构建的应⽤程序的⽬标是尽可能的解耦和尽可能的内聚——他 们拥有⾃⼰的领域逻辑,他们的⾏为更像经典 UNIX 理念中的过滤器⸺接收请求,应⽤适当的逻辑并产⽣响应。使⽤简单的 REST ⻛格的协议来编排它们,⽽不是使⽤像 WS-Choreography 或者 BPEL 或者通过中⼼⼯具编制(orchestration)等复杂的协 议。最常⽤的两种协议是带有资源 API 的 HTTP 请求-响应和轻量级消息传递[8]。对第⼀种协议最好的表述是
本⾝就是 web,⽽不是隐藏在 web 的后⾯。——Ian Robinson 微服务团队使⽤的规则和协议,正是构建万维⽹的规则和协议(在更⼤程度上是 UNIX 的)。从开发者和运营⼈员的⻆度讲,通常使 ⽤的资源可以很容易的缓存。 第⼆种常⽤⽅法是在轻量级消息总线上传递消息。选择的基础设施是典型的哑的(哑在这⾥只充当消息路由器)⸺像 RabbitMQ 或 ZeroMQ 这样简单的实现仅仅提供⼀个可靠的异步交换结构 ⸺在服务⾥,智能特性仍旧存在于那些⽣产和消费诸多消息的各 个端点中,即存在于各个服务中。 单体应⽤中,组件都在同⼀进程内执⾏,它们之间通过⽅法调⽤或函数调⽤通信。把单体变成微服务最⼤的问题在于通信模式的 改变。⼀种幼稚的转换是从内存⽅法调⽤转变成 RPC,这导致频繁通信且性能不好。相反,你需要⽤粗粒度通信代替细粒度通 信。去中⼼化的治理 集中治理的⼀个后果是单⼀技术平台的标准化发展趋势。经验表明,这种⽅法正在收缩 ⸺不是每个问题都是钉⼦,不是每个问 题都是锤⼦。我们更喜欢使⽤正确的⼯具来完成⼯作,⽽单体应⽤程序在⼀定程度上可以利⽤语⾔的优势,这是不常⻅的。 把单体的组件分裂成服务,在构建这些服务时可以有⾃⼰的选择。你想使⽤ Node.js 开发⼀个简单的报告⻚⾯?去吧。⽤ C++ 实 现⼀个特别粗糙的近乎实时的组件?好极了。你想换⽤⼀个更适合组件读操作数据的不同⻛格的数据库?我们有技术来重建它。 当然,仅仅因为你可以做些什么,⽽不意味着你应该这样做——但⽤这种⽅式划分系统意味着你可以选择。 团队在构建微服务时也更喜欢⽤不同的⽅法来达标。他们更喜欢⽣产有⽤的⼯具这种想法,⽽不是写在纸上的标准,这样其他开 发者可以⽤这些⼯具解决他们所⾯临的相似的问题。有时,这些⼯具通常在实施中收获并与更⼴泛的群体共享,但不完全使⽤⼀ 个内部开源模型。现在 git 和 github 已经成为事实上的版本控制系统的选择,在内部开放源代码的实践也正变得越来越常⻅。 Netflix 是遵循这⼀理念的⼀个很好的例⼦。尤其是,以库的形式分享有⽤的且经过市场检验的代码,这激励其他开发者⽤类似的 ⽅式解决相似的问题,同时还为采⽤不同⽅法敞开了⼤⻔。共享库倾向于聚焦在数据存储、进程间通信和我们接下来要深⼊讨论 的基础设施⾃动化的共性问题。 对于微服务社区来说,开销特别缺乏吸引⼒。这并不是说社区不重视服务合约。恰恰相反,因为他们有更多的合约。只是他们正 在寻找不同的⽅式来管理这些合约。像 Tolerant Reader 和 Consumer-Driven Contracts 这样的模式通常被⽤于微服务。这些 援助服务合约在独⽴进化。执⾏消费者驱动的合约作为构建的⼀部分,增加了信⼼并对服务是否在运作提供了更快的反馈。事实 上,我们知道澳⼤利亚的⼀个团队⽤消费者驱动的合约这种模式来驱动新业务的构建。他们使⽤简单的⼯具定义服务的合约。这 已变成⾃动构建的⼀部分,即使新服务的代码还没写。服务仅在满⾜合约的时候才被创建出来 - 这是在构建新软件时避免 “YAGNI”[9] 困境的⼀个优雅的⽅法。围绕这些成⻓起来的技术和⼯具,通过减少服务间的临时耦合,限制了中⼼合约管理的需 要。也许去中⼼化治理的最⾼境界就是亚⻢逊⼴为流传的 build it/run it 理念。团队要对他们构建的软件的各⽅⾯负责,包括 724 ⼩ 时的运营。这⼀级别的责任下放绝对是不规范的,但我们看到越来越多的公司让开发团队负起更多责任。Netflix 是采⽤这⼀理念 的另⼀家公司[11]。每天凌晨 3 点被传呼机叫醒⽆疑是⼀个强有⼒的激励,使你在写代码时关注质量。这是关于尽可能远离传统 的集中治理模式的⼀些想法。 分散数据管理 数据管理的去中⼼化有许多不同的呈现⽅式。在最抽象的层⾯上,这意味着使系统间存在差异的世界概念模型。在整合⼀个⼤型 企业时,客⼾的销售视图将不同于⽀持视图,这是⼀个常⻅的问题。客⼾的销售视图中的⼀些事情可能不会出现在⽀持视图中。
它们确实可能有不同的属性和(更坏的)共同属性,这些共同属性在语义上有微妙的不同。 这个问题常⻅于应⽤程序之间,但也可能发⽣在应⽤程序内部,尤其当应⽤程序被划分成分离的组件时。⼀个有⽤的思维⽅式是 有界上下⽂(Bounded Context)内的领域驱动设计(Domain-Driven Design, DDD)理念。DDD 把⼀个复杂域划分成多个有界的上 下⽂,并且映射出它们之间的关系。这个过程对单体架构和微服务架构都是有⽤的,但在服务和上下⽂边界间有天然的相关性, 边界有助于澄清和加强分离,就像业务能⼒部分描述的那样。 和概念模型的去中⼼化决策⼀样,微服务也去中⼼化数据存储决策。虽然单体应⽤程序更喜欢单⼀的逻辑数据库做持久化存储, 但企业往往倾向于⼀系列应⽤程序共⽤⼀个单⼀的数据库——这些决定是供应商授权许可的商业模式驱动的。微服务更倾向于让 每个服务管理⾃⼰的数据库,或者同⼀数据库技术的不同实例,或完全不同的数据库系统 - 这就是所谓的混合持久化(Polyglot Persistence)。你可以在单体应⽤程序中使⽤混合持久化,但它更常出现在为服务⾥。 对跨微服务的数据来说,去中⼼化责任对管理升级有影响。处理更新的常⽤⽅法是在更新多个资源时使⽤事务来保证⼀致性。这 个⽅法通常⽤在单体中。 像这样使⽤事务有助于⼀致性,但会产⽣显著地临时耦合,这在横跨多个服务时是有问题的。分布式事务是出了名的难以实现, 因此微服务架构强调服务间的⽆事务协作,对⼀致性可能只是最后⼀致性和通过补偿操作处理问题有明确的认知。 对很多开发团队来说,选择⽤这样的⽅式管理不⼀致性是⼀个新的挑战,但这通常与业务实践相匹配。通常业务处理⼀定程度的 不⼀致,以快速响应需求,同时有某些类型的逆转过程来处理错误。这种权衡是值得的,只要修复错误的代价⼩于更⼤⼀致性下 损失业务的代价。 基建⾃动化 基础设施⾃动化技术在过去⼏年中发⽣了巨⼤变化——特别是云和 AWS 的发展降低了构建、部署和运⾏微服务的操作复杂性。 许多使⽤微服务构建的产品或系统都是由具有丰富的持续交付和持续集成经验的团队构建的。以这种⽅式构建软件的团队⼴泛使 ⽤基础设施⾃动化技术。如下⾯显⽰的构建管道所⽰。
由于这并不是⼀篇关于持续交付的⽂章,我们在这⾥只关注持续交付的⼏个关键特性。我们希望有尽可能多的信⼼确保我们的软 件正常运⾏,因此我们进⾏了⼤量的⾃⾃动动化化测测试试。想让软件达到“晋级”(Promotion)状态从⽽“推上”流⽔线,就意味着要在 每⼀个新的环境中,对软件进⾏⾃⾃动动化化部部署署。 ⼀个单体应⽤程序可以⾮常愉快地通过这些环境构建、测试和推动。事实证明,⼀旦你为单体投⼊了⾃动化整体⽣产,那么部署 更多的应⽤程序似乎不再那么可怕了。请记住,持续交付的⽬标之⼀就是让“部署”⼯作变得“枯燥”,所以⽆论是⼀个还是三 个应⽤程序,只要部署⼯作依旧很“枯燥”,那么就没什么可担⼼的了[12]。 我们看到团队⼤量的基础设施⾃动化的另⼀个领域是在管理⽣产环境中的微服务。与我们上⾯的断⾔(只要部署很⽆聊)相⽐, 单体和微服务之间没有太⼤的区别,但是每个部署的运⾏环境可能会截然不同。 设计时为故障做好准备 使⽤服务作为组件的结果是,需要设计应⽤程序以便它们能够容忍服务的失败。如果服务提供者商不可⽤,任何服务呼叫都可能 失败,客⼾必须尽可能优雅地对此做出响应。与单体设计相⽐,这是⼀个缺点,因为它这会引⼊额外的复杂性来处理它。结果是 微服务团队不断反思服务失败是如何影响⽤⼾体验的。Netflix 的 Simian Army 能够引发服务甚⾄数据中⼼的故障在⼯作⽇发⽣ 故障,从⽽来测试应⽤程序的弹性和监控能⼒。 ⽣产中的这种⾃动化测试⾜以让⼤多数运维团队兴奋得浑⾝颤栗,就像在⼀周的⻓假即将到来前⼀样。这并不是说单体架构⻛格 不能构建先进的监控系统——只是根据我们的经验,这在单体系统中并不常⻅罢了。 由于服务可能随时发⽣故障,因此能够快速检测故障并在可能的情况下⾃动恢复服务就显得⾄关重要。微服务应⽤程序⾮常重视 应⽤程序的实时监控,⽐如检查架构元素(数据库每秒获得多少请求)和业务相关度量(例如每分钟收到多少订单)。语义监控 可以提供出现问题的早期预警系统,从⽽触发开发团队跟进和调查。 这对于微服务架构来说尤为重要,因为微服务偏好编排和事件写作,这会导致⼀些紧急状况。虽然许多权威⼈⼠对于偶然事件的 价值持积极态度,但事实是,“突发⾏为”有时可能是⼀件坏事。监控⾄关重要,它能够快速发现不良紧急⾏为并进⾏修复。
单体系统也可以像微服务⼀样实现透明的监控——事实上,它们也应该如此。不同之处在于你必须能够知道在不同进程中运⾏的 服务在何时断开了连接。对于同⼀过程中的库,这种透明性⽤处并不⼤。 微服务团队希望看到针对每个服务的复杂监控和⽇志记录,例如显⽰“运⾏/宕机”状态的仪表盘以及各种运维和业务相关的指 标。有关断路器状态,当前吞吐量和延迟的详细信息也是我们在⼯作中经常遇到的其他例⼦。 演化设计 微服务从业者通常有进化设计的背景,并把服务分解视为进⼀步的⼯具,使应⽤程序开发⼈员能够控制应⽤程序中的更改,⽽不 会降低变更速度。变更控制并不⼀定意味着变更的减少——在正确的态度和⼯具的帮助下,你可以对软件进⾏频繁,快速且有良 好控制的更改。 每当要试图将软件系统分解为组件时,你就会⾯临这样的决策,即如何进⾏拆分——我们决定拆分应⽤程序的原则是什么?组件 的关键属性具有独⽴替换和可升级性的特点[13]——这意味着我们寻找这些点,想象如何在不影响其协作者的情况下重写组件。 实际上,许多微服务组通过明确地期望许多服务被废弃⽽不是⻓期演变来进⼀步考虑这⼀点。 Guardian ⽹站是设计和构建成单体应⽤程序的⼀个很好的例⼦,但是它也在微服务⽅向上不断发展演化。原先的单体系统仍然 是⽹站的核⼼,但他们更喜欢通过构建⼀些微服务 API 的⽅式来添加新的功能。这种⽅法对于本质上是临时的功能尤其⽅便,例 如处理体育赛事的专⽤⻚⾯。⽹站的这⼀部分可以使⽤快速开发语⾔快速组合在⼀起,在赛事结束后⽴即删除。我们在⾦融机构 看到过类似的⽅法,为市场机会增加新服务,并在⼏个⽉甚⾄⼏周后丢弃。 这种强调可替换性的特点,是模块化设计⼀般性原则的⼀个特例,即通过变化模式来驱动模块化的实现[14]。⼤家都愿意将那些 同时发⽣变化的东西放在同⼀个模块,很少变化的系统模块应该与⽬前正在经历⼤量变动的系统处于不同的服务中。如果你发现 ⾃⼰反复更改两项服务,那就表明它们应该合并了。 将组件放⼊服务中可以为更细粒度的发布计划添加机会。对于单体来说,任何更改都需要完整构建和部署整个应⽤程序。但是, 使⽤微服务,你只需要重新部署你修改的服务。这可以简化并加快发布过程。缺点是你必须担⼼⼀项服务的变化会打破其消费 者。传统的集成⽅法是尝试使⽤版本控制来解决这个问题,但微服务世界中的偏好是仅仅把使⽤版本控制作为最后的⼿段。我们 可以通过设计服务尽可能容忍服务提供者的变化来避免⼤量的版本控制。 微服务是未来吗? 我们写这篇⽂章的主要⽬的是解释微服务的主要思想和原则。通过花时间来做到这⼀点,我们清楚地认为微服务架构⻛格是⼀个 重要的想法——在研发企业系统时,值得对它进⾏认真考虑。我们最近使⽤这种⽅式构建了⼏个系统,并且了解到其它团队也赞 同这种⻛格。 我们了解到那些在某种程度上开创这种架构⻛格的先驱,包括亚⻢逊、Netflix、英国卫报、英国政府数字化服务中⼼、 realestate.com.au、Forward 和 comparethemarket.com。2013 年的技术会议上充满了⼀些公司的例⼦,这些公司正在转向 可以归类为微服务的公司,包括 Travis CI。此外,有很多组织⻓期以来⼀直在做我们称之为微服务的东西,但没有使⽤过这个名 字。(通常这被标记为 SOA——尽管如我们所说,SOA 有许多相互⽭盾的形式。[15]) 然⽽,尽管有这些积极的经验,但并不是说我们确信微服务是软件架构的未来发展⽅向。虽然到⽬前为⽌我们的经验与整体应⽤ 相⽐是积极的,但我们意识到没有⾜够的时间让我们做出充分完整的判断。 通常,架构决策所产⽣的真正效果,只有在该决策做出若⼲年后才能真正显现。我们已经看到由带着强烈的模块化愿望的优秀团 队所做的⼀些项⽬,最终却构建出⼀个单体架构,并在⼏年之内不断腐化。许多⼈认为,如果使⽤微服务就不⼤可能出现这种腐 化,因为服务的边界是明确的,⽽且难以随意搞乱。然⽽,对于那些开发时间⾜够⻓的各种系统,除⾮我们已经⻅识得⾜够多, 否则我们⽆法真正评价微服务架构是如何成熟的。
有⼈觉得微服务或许很难成熟起来,这当然是有原因的。在组件化上所做的任何⼯作的成功与否,取决于软件与组件的匹配程 度。准确地搞清楚某个组件的边界的位置应该出现在哪⾥,是⼀项困难的⼯作。进化设计承认难以对边界进⾏正确定位,所以它 将⼯作的重点放到了易于对边界进⾏重构之上。但是当各个组件成为各个进⾏远程通信的服务后,⽐起在单⼀进程内进⾏各个软 件库之间的调⽤,此时的重构就变得更加困难。跨越服务边界的代码移动就变得困难起来。接⼝的任何变化,都需要在其各个参 与者之间进⾏协调。向后兼容的层次也需要被添加进来。测试也会变得更加复杂。 另⼀个问题是,如果这些组件不能⼲净利落地组合成⼀个系统,那么所做的⼀切⼯作,仅仅是将组件内的复杂性转移到组件之间 的连接之上。这样做的后果,不仅仅是将复杂性搬了家,它还将复杂性转移到那些不再明确且难以控制的边界之上。当在观察⼀ 个⼩型且简单的组件内部时,⼈们很容易觉得事情已经变得更好了,然⽽他们却忽视了服务之间杂乱的连接。 最后,还有⼀个团队技能的因素。新技术往往会被技术更加过硬的团队所采⽤。对于技术更加过硬的团队⽽更有效的⼀项技术, 不⼀定适⽤于⼀个技术略逊⼀筹的团队。我们已经看到⼤量这样的案例,那些技术略逊⼀筹的团队构建出了杂乱的单体架构。当 这种杂乱发⽣到微服务⾝上时,会出现什么情况?这需要花时间来观察。⼀个糟糕的团队,总会构建⼀个糟糕的系统——在这种 情况下,很难讲微服务究竟是减少了杂乱,还是让事情变得更糟。 我们听到的⼀个合理的论点是,你不应该从微服务架构开始,⽽是从整体开始,保持模块化,并在整体出现问题时将其拆分为微 服务。(这个建议并不理想,因为好的进程内接⼝通常不是⼀个好的服务接⼝。) 所以我们谨慎乐观地写下这个。到⽬前为⽌,我们已经看到了⾜够多的微服务⻛格,觉得它可能是⼀条值得⾛的路。我们⽆法确 定最终会在哪⾥结束,但软件开发的挑战之⼀是你只能根据你当前必须拥有的不完善信息做出决策。 tips:欢迎关注微信公众号:Java后端,获取每⽇技术博⽂推送。 脚注1: The term “microservice” was discussed at a workshop of software architects near Venice in May, 2011 to describe what the participants saw as a common architectural style that many of them had been recently exploring. In May 2012, the same group decided on “microservices” as the most appropriate name. James presented some of these ideas as a case study in March 2012 at 33rd Degree in Krakow in Microservices - Java, the Unix Way as did Fred George about the same time. Adrian Cockcroft at Netflix, describing this approach as “fine grained SOA” was pioneering the style at web scale as were many of the others mentioned in this article - Joe Walnes, Dan North, Evan Botcher and Graham Tackley. 2: The term monolith has been in use by the Unix community for some time. It appears in The Art of Unix Programming to describe systems that get too big. 3: Many object-oriented designers, including ourselves, use the term service object in the Domain-Driven Design sense for an object that carries out a significant process that isn’t tied to an entity. This is a different concept to how we’re using “service” in this article. Sadly the term service has both meanings and we have to live with the polyseme. 4: We consider an application to be a social construction that binds together a code base, group of functionality, and body of funding. 5: The original paper can be found on Melvyn Conway’s website here. 6: We can’t resist mentioning Jim Webber’s statement that ESB stands for “Egregious Spaghetti Box”. 7: Netflix makes the link explicit - until recently referring to their architectural style as fine-grained SOA.
8: At extremes of scale, organisations often move to binary protocols - protobufs for example. Systems using these still exhibit the characteristic of smart endpoints, dumb pipes - and trade off transparency for scale. Most web properties and certainly the vast majority of enterprises don’t need to make this tradeoff - transparency can be a big win. 9: “YAGNI” or “You Aren’t Going To Need It” is an XP principle and exhortation to not add features until you know you need them. 10: It’s a little disengenuous of us to claim that monoliths are single language - in order to build systems on todays web, you probably need to know JavaScript and XHTML, CSS, your server side language of choice, SQL and an ORM dialect. Hardly single language, but you know what we mean. 11: Adrian Cockcroft specifically mentions “developer self-service” and “Developers run what they wrote”(sic) in this excellent presentation delivered at Flowcon in November, 2013. 12: We are being a little disengenuous here. Obviously deploying more services, in more complex topologies is more difficult than deploying a single monolith. Fortunately, patterns reduce this complexity - investment in tooling is still a must though. 13: In fact, Dan North refers to this style asReplaceable Component Architecture rather than microservices. Since this seems to talk to a subset of the characteristics we prefer the latter. 14: Kent Beck highlights this as one his design principles in Implementation Patterns. 15: And SOA is hardly the root of this history. I remember people saying “we’ve been doing this for years” when the SOA term appeared at the beginning of the century. One argument was that this style sees its roots as the way COBOL programs communicated via data files in the earliest days of enterprise computing. In another direction, one could argue that microservices are the same thing as the Erlang programming model, but applied to an enterprise application context. -E N D- 如果看到这⾥,说明你喜欢这篇⽂章,帮忙转转发发⼀下吧,感谢。微信搜索「web_resource」,关注后即可获取每⽇⼀题的推送。 推荐阅读 11… 每⽇⼀题:消息队列⾯试常问题⽬ 22… 每⽇⼀题:如何保证消息队列的⾼可⽤? 33… 每⽇⼀题:如何设计⼀个⾼并发系统? 44… 每⽇⼀题:为什么要进⾏系统拆分? 55… 每⽇⼀题:你有没有做过 MySQL 读写分离? 66… 每⽇⼀题:Java 线程池 8 ⼤拒绝策略 77… 每⽇⼀题:分布式事务 88… 让你写⼀个消息队列,该如何设计? 99… ⼤⼚⾯试官最喜欢问的⾯试难点 1100… 分享 100 道 Linux 笔试题 1111… InnoDB 中⼀棵 B+ 树能存多少⾏数据?
阅读原⽂ 声明:pdf仅供学习使⽤,⼀切版权归原创公众号所有;建议持续关注原创公众号获取最新⽂章,学习愉快! 1122… 每⽇⼀题:如何保证消息的顺序性? 1133… Dubbo RPC 常问⾯试题整理好了,来拿! 1144… 每⽇⼀题:如何保证消息不被重复消费? 1155… 每⽇⼀题:Zookeeper 都有哪些使⽤场景? 喜欢⽂章,点个在在看看
秒杀架构模型设计 Yrion Java后端 2019-12-06 点击上⽅ Java后端,选择 设为星标 优质⽂章,及时送达 作者 | Yrion 来源 | www.cnblogs.com/wyq178/p/11261711.html 前⾔:秒杀系统相信很多⼈⻅过,⽐如京东或者淘宝的秒杀,⼩⽶⼿机的秒杀,那么秒杀系统的后台是如何实现的呢?我们如 何设计⼀个秒杀系统呢?对于秒杀系统应该考虑哪些问题?如何设计出健壮的秒杀系统?本期我们就来探讨⼀下这个问题: 博客的⽬录 ⼀⼀::秒秒杀杀系系统统应应该该考考虑虑的的问问题题 ⼆⼆::秒秒杀杀系系统统的的设设计计和和技技术术⽅⽅案案 三三::系系统统架架构构图图 四四::总总结结 ⼀⼀::秒秒杀杀应应该该考考虑虑哪哪些些问问题题 1.1:超卖问题 分析秒杀的业务场景,最重要的有⼀点就是超卖问题,假如备货只有100个,但是最终超卖了200,⼀般来讲秒杀系统的价格 都⽐较低,如果超卖将严重影响公司的财产利益,因此⾸当其冲的就是解决商品的超卖问题。 1.2:⾼并发 秒杀具有时间短、并发量⼤的特点,秒杀持续时间只有⼏分钟,⽽⼀般公司都为了制造轰动效应,会以极低的价格来吸引⽤ ⼾,因此参与抢购的⽤⼾会⾮常的多。短时间内会有⼤量请求涌进来,后端如何防⽌并发过⾼造成缓存击穿或者失效,击垮 数据库都是需要考虑的问题。 1.3:接⼝防刷 现在的秒杀⼤多都会出来针对秒杀对应的软件,这类软件会模拟不断向后台服务器发起请求,⼀秒⼏百次都是很常⻅的,如 何防⽌这类软件的重复⽆效请求,防⽌不断发起的请求也是需要我们针对性考虑的 1.4:秒杀url 对于普通⽤⼾来讲,看到的只是⼀个⽐较简单的秒杀⻚⾯,在未达到规定时间,秒杀按钮是灰⾊的,⼀旦到达规定时间,灰⾊ 按钮变成可点击状态。这部分是针对⼩⽩⽤⼾的,如果是稍微有点电脑功底的⽤⼾,会通过F12看浏览器的network看到秒 杀的url,通过特定软件去请求也可以实现秒杀。或者提前知道秒杀url的⼈,⼀请求就直接实现秒杀了。这个问题我们需要 考虑解决 1.5:数据库设计
秒杀有把我们服务器击垮的⻛险,如果让它与我们的其他业务使⽤在同⼀个数据库中,耦合在⼀起,就很有可能牵连和影响 其他的业务。如何防⽌这类问题发⽣,就算秒杀发⽣了宕机、服务器卡死问题,也应该让他尽量不影响线上正常进⾏的业务 1.6:⼤量请求问题 按照1.2的考虑,就算使⽤缓存还是不⾜以应对短时间的⾼并发的流量的冲击。如何承载这样巨⼤的访问量,同时提供稳定 低时延的服务保证,是需要⾯对的⼀⼤挑战。我们来算⼀笔账,假如使⽤的是redis缓存,单台redis服务器可承受的QPS⼤ 概是4W左右,如果⼀个秒杀吸引的⽤⼾量⾜够多的话,单QPS可能达到⼏⼗万,单体redis还是不⾜以⽀撑如此巨⼤的请求 量。缓存会被击穿,直接渗透到DB,从⽽击垮mysql.后台会将会⼤量报错 ⼆⼆::秒秒杀杀系系统统的的设设计计和和技技术术⽅⽅案案 2.1:秒杀系统数据库设计 针对1.5提出的秒杀数据库的问题,因此应该单独设计⼀个秒杀数据库,防⽌因为秒杀活动的⾼并发访问拖垮整个⽹站。这 ⾥只需要两张表,⼀张是秒杀订单表,⼀张是秒杀货品表 其实应该还有⼏张表,商品表:可以关联goods_id查到具体的商品信息,商品图像、名称、平时价格、秒杀价格等,还有⽤⼾ 表:根据⽤⼾user_id可以查询到⽤⼾昵称、⽤⼾⼿机号,收货地址等其他额外信息,这个具体就不给出实例了。 2.2:秒杀url的设计 为了避免有程序访问经验的⼈通过下单⻚⾯url直接访问后台接⼝来秒杀货品,我们需要将秒杀的url实现动态化,即使是开 发整个系统的⼈都⽆法在秒杀开始前知道秒杀的url。具体的做法就是通过md5加密⼀串随机字符作为秒杀的url,然后前 端访问后台获取具体的url,后台校验通过之后才可以继续秒杀。 2.3:秒杀⻚⾯静态化 将商品的描述、参数、成交记录、图像、评价等全部写⼊到⼀个静态⻚⾯,⽤⼾请求不需要通过访问后端服务器,不需要经过 数据库,直接在前台客⼾端⽣成,这样可以最⼤可能的减少服务器的压⼒。具体的⽅法可以使⽤freemarker模板技术,建⽴ ⽹⻚模板,填充数据,然后渲染⽹⻚ 2.4:单体redis升级为集群redis 秒杀是⼀个读多写少的场景,使⽤redis做缓存再合适不过。不过考虑到缓存击穿问题,我们应该构建redis集群,采⽤哨兵 模式,可以提升redis的性能和可⽤性。 2.5:使⽤nginx nginx是⼀个⾼性能web服务器,它的并发能⼒可以达到⼏万,⽽tomcat只有⼏百。通过nginx映射客⼾端请求,再分发到 后台tomcat服务器集群中可以⼤⼤提升并发能⼒。 2.6:精简sql
典型的⼀个场景是在进⾏扣减库存的时候,传统的做法是先查询库存,再去update。这样的话需要两个sql,⽽实际上⼀个 sql我们就可以完成的。可以⽤这样的做法:update miaosha_goods set stock =stock-1 where goos_id = {#goods_id} and version = #{version} and sock>0;这样的话,就可以保证库存不会超卖并且⼀次更新库存,还有注意 ⼀点这⾥使⽤了版本号的乐观锁,相⽐较悲观锁,它的性能较好。 2.7:redis预减库存 很多请求进来,都需要后台查询库存,这是⼀个频繁读的场景。可以使⽤redis来预减库存,在秒杀开始前可以在redis设值, ⽐如redis.set(goodsId,100),这⾥预放的库存为100可以设值为常量),每次下单成功之后,Integer stock = (Integer)redis.get(goosId); 然后判断sock的值,如果⼩于常量值就减去1;不过注意当取消的时候,需要增加库存,增加 库存的时候也得注意不能⼤于之间设定的总库存数(查询库存和扣减库存需要原⼦操作,此时可以借助lua脚本)下次下单再 获取库存的时候,直接从redis⾥⾯查就可以了。 2.8:接⼝限流 秒杀最终的本质是数据库的更新,但是有很多⼤量⽆效的请求,我们最终要做的就是如何把这些⽆效的请求过滤掉,防⽌渗 透到数据库。限流的话,需要⼊⼿的⽅⾯很多: 2.9.1:前端限流 ⾸先第⼀步就是通过前端限流,⽤⼾在秒杀按钮点击以后发起请求,那么在接下来的5秒是⽆法点击(通过设置按钮为 disable)。这⼀⼩举措开发起来成本很⼩,但是很有效。 2.9.2:同⼀个⽤⼾xx秒内重复请求直接拒绝 具体多少秒需要根据实际业务和秒杀的⼈数⽽定,⼀般限定为10秒。具体的做法就是通过redis的键过期策略,⾸先对每个 请求都从String value = redis.get(userId);如果获取到这个 value为空或者为null,表⽰它是有效的请求,然后放⾏这个请求。如果不为空表⽰它是重复性请求,直接丢掉这个请求。如 果有效,采⽤redis.setexpire(userId,value,10).value可以是任意值,⼀般放业务属性⽐较好,这个是设置以userId为 key,10秒的过期时间(10秒后,key对应的值⾃动为null) 2.7.3:令牌桶算法限流 接⼝限流的策略有很多,我们这⾥采⽤令牌桶算法。令牌桶算法的基本思路是每个请求尝试获取⼀个令牌,后端只处理持有 令牌的请求,⽣产令牌的速度和效率我们都可以⾃⼰限定,guava提供了RateLimter的api供我们使⽤。以下做⼀个简单的 例⼦,注意需要引⼊guava ppuubblliicc ccllaassss TTeessttRRaatteeLLiimmiitteerr { ppuubblliicc ssttaattiicc vvooiidd mmaaiinn(String[] args) { //1秒产⽣1个令牌 final RateLimiter rateLimiter = RateLimiter.create(1); ffoorr (iinntt i = 0; i < 10; i++) { //该⽅法会阻塞线程,直到令牌桶中能取到令牌为⽌才继续向下执⾏。 ddoouubbllee waitTime= rateLimiter.acquire(); System.oouutt.println(“任务执⾏” + i + “等待时间” + waitTime); }System.oouutt.println(“执⾏结束”); } }
上⾯代码的思路就是通过RateLimiter来限定我们的令牌桶每秒产⽣1个令牌(⽣产的效率⽐较低),循环10次去执⾏任务。 acquire会阻塞当前线程直到获取到令牌,也就是如果任务没有获取到令牌,会⼀直等待。那么请求就会卡在我们限定的时 间内才可以继续往下⾛,这个⽅法返回的是线程具体等待的时间。执⾏如下; 可以看到任务执⾏的过程中,第1个是⽆需等待的,因为已经在开始的第1秒⽣产出了令牌。接下来的任务请求就必须等到令 牌桶产⽣了令牌才可以继续往下执⾏。如果没有获取到就会阻塞(有⼀个停顿的过程)。不过这个⽅式不太好,因为⽤⼾如果 在客⼾端请求,如果较多的话,直接后台在⽣产token就会卡顿(⽤⼾体验较差),它是不会抛弃任务的,我们需要⼀个更优秀 的策略:如如果果超超过过某某个个时时间间没没有有获获取取到到,,直直接接拒拒绝绝该该任任务务。接下来再来个案例: ppuubblliicc ccllaassss TTeessttRRaatteeLLiimmiitteerr22 { ppuubblliicc ssttaattiicc vvooiidd mmaaiinn(String[] args) { final RateLimiter rateLimiter = RateLimiter.create(1); ffoorr (iinntt i = 0; i < 10; i++) { lloonngg timeOut = (lloonngg) 0.5; boolean isValid = rateLimiter.tryAcquire(timeOut, TimeUnit.SECONDS); System.oouutt.println(“任务” + i + “执⾏是否有效:” + isValid); iiff (!isValid) { ccoonnttiinnuuee; }System.oouutt.println(“任务” + i + “在执⾏”); }System.oouutt.println(“结束”); } }其中⽤到了tryAcquire⽅法,这个⽅法的主要作⽤是设定⼀个超时的时间,如果在指定的时间内预预估估((注注意意是是预预估估并并不不会会真真 实实的的等等待待)),,如果能拿到令牌就返回true,如果拿不到就返回false.然后我们让⽆效的直接跳过,这⾥设定每秒⽣产1个令 牌,让每个任务尝试在 0.5秒获取令牌,如果获取不到,就直接跳过这个任务(放在秒杀环境⾥就是直接抛弃这个请求);程序实际运⾏如下: 只有第1个获取到了令牌,顺利执⾏了,下⾯的基本都直接抛弃了,因为0.5秒内,令牌桶(1秒1个)来不及⽣产就肯定获取不 到返回false了。
这这个个限限流流策策略略的的效效率率有有多多⾼⾼呢呢??假假如如我我们们的的并并发发请请求求是是440000万万瞬瞬间间的的请请求求,将将令令牌牌产产⽣⽣的的效效率率设设为为每每秒秒2200个个,,每每次次尝尝试试获获取取 令令牌牌的的时时间间是是00…0055秒秒,,那那么么最最终终测测试试下下来来的的结结果果是是,,每每次次只只会会放放⾏⾏44个个左左右右的的请请求求,⼤⼤量量的的请请求求会会被被拒拒绝绝,这这就就是是令令牌牌桶桶算算法法 的的优优秀秀之之处处。。 2.8:异步下单 为了提升下单的效率,并且防⽌下单服务的失败。需要将下单这⼀操作进⾏异步处理。最常采⽤的办法是使⽤队列,队列最 显著的三个优点:异异步步、、削削峰峰、、解解耦耦。这⾥可以采⽤rabbitmq,在后台经过了限流、库存校验之后,流⼊到这⼀步骤的就是有 效请求。然后发送到队列⾥,队列接受消息,异步下单。下完单,⼊库没有问题可以⽤短信通知⽤⼾秒杀成功。假如失败的话, 可以采⽤补偿机制,重试。 2.9:服务降级 假如在秒杀过程中出现了某个服务器宕机,或者服务不可⽤,应该做好后备⼯作。之前的博客⾥有介绍通过Hystrix进⾏服 务熔断和降级,可以开发⼀个备⽤服务,假如服务器真的宕机了,直接给⽤⼾⼀个友好的提⽰返回,⽽不是直接卡死,服务器 错误等⽣硬的反馈。 三:总结 秒杀流程图: 这就是我设计出来的秒杀流程图,当然不同的秒杀体量针对的技术选型都不⼀样,这个流程可以⽀撑起⼏⼗万的流量,如果 是成千万破亿那就得重新设计了。⽐如数据库的分库分表、队列改成⽤kafka、redis增加集群数量等⼿段。通过本次设计主 要是要表明的是我们如何应对⾼并发的处理,并开始尝试解决它,在⼯作中多思考、多动⼿能提升我们的能⼒⽔平,加油!如 果本篇博客有任何错误,请⿇烦指出来,不胜感激。 【END】 如果看到这⾥,说明你喜欢这篇⽂章,请转转发发、、点点赞赞。微信搜索「web_resource」,关注后回复「进群」或者扫描下⽅⼆维码 即可进⼊⽆⼴告交流群。 ↓扫扫描描⼆⼆维维码码进进群群↓↓
阅读原⽂ 声明:pdf仅供学习使⽤,⼀切版权归原创公众号所有;建议持续关注原创公众号获取最新⽂章,学习愉快! 推荐阅读 11… Spring Boot:启动原理解析 22… ⼀键下载 Pornhub 视频! 33… Spring Boot 多模块项⽬实践(附打包⽅法) 44… ⼀个⼥⽣不主动联系你还有机会吗? 55… 团队开发中 Git 最佳实践 喜欢⽂章,点个在在看看
轻量型互联⽹应⽤架构⽅式 天如 Java后端 2019-12-08 点击上⽅ Java后端,选择 设为星标 优质⽂章,及时送达 作者 | 天如 链接 | http://suo.im/4qRPkj ⼀⼀、、前前⾔⾔ 说到互联⽹应⽤架构,就绕不开微服务,当下(2019)最热⻔的微服务架构体系应该还是 Spring Cloud 和 Dubbo,阿⾥也推出 了⾃⼰的 Spring Cloud 实现 Spring Cloud Alibaba。这类框架围绕微服务体系提供了⼤⽽全的功能,包括服务发现、治理、流 量监控、配置管理等,让⼈向往。 但是其缺点也是⽐较明显: 限定于 Java 体系,其他编程语⾔⽆法享受这个体系 侵⼊应⽤,⼏乎每个 Java 应⽤都挂载⼀堆 Spring 相关的 jar 包 所以⽬前社区发展的新⽅向是:云原⽣。 其中的翘楚便是 istio,基于 kubernetes 的 sidecar 模式,把 Sprint Cloud 做的⼤部分⼯作下沉到基础应⽤层⾯,让应⽤层⽆ 感。有了 Istio 之后,微服务应⽤实现了⼤减负,所以我重新思考了对于⼩型互联⽹公司来说,当下最适合的架构模式应该是什么。 ⼆⼆、、WWeebb 架架构构模模式式 适⽤场景:⼩型互联⽹公司,业务多⽽⼩,追求开发效率。 解决⽅案:把重复⼯作隐藏起来,让应⽤层轻量快跑 具体有两个⽅向 基础架构层⾯:使⽤ Kubernetes 和 Istio,提供微服务所需要的功能⽀持 应⽤层⾯:针对 Nodejs 后端编程领域,提供⼀个最佳实额 整体架构图:
三三、、基基础础架架构构实实践践 ⽬前还没有⾜够的时间完成落地实践,只能说⼀下理论规划。 现在是云时代,所以直接⽤ Kubernetes 来做应⽤编排就可以了。 后期可以落地 Istio,就可以保证微服务们稳定运⾏。 容容器器编编排排 Kubernetes ⽇⽇志志收收集集在 k8s 集成 elk 输⼊⽇志到阿⾥云 nas 应应⽤⽤观观测测 Istio 提供 Grafana 请求分析、 Jaeger链路追踪、告警等 流流量量控控制制权限校验 加密熔断限流 四四、、WWeebb 应应⽤⽤架架构构
在应⽤层,我最终选择是使⽤ Node.js 语⾔来做主要的业务开发。 使⽤ Koa + ⾃研框架。 语语⾔⾔的的选选择择,,为为什什么么是是 NNooddee…jjss 主要原因是我⽬前所在团队技术栈在 Node 积累⽐较多。 其次原因是有了 typescript 之后,使⽤ node 编写后端应⽤变得更加可维护了,⽽且 node ⼩⽽快(开发效率快,当然IO密集型 的应⽤运⾏效率也不差)⾮常适合微服务场景。 最后,Node 的上⼿难度真的很低。 当然要使⽤其他语⾔也是可以的,如果有着复杂业务逻辑,可以使⽤ Kotlin + Spring Boot 这组套件,但不包括 Spring Cloud。 Kotlin 能完美复⽤ JVM ⽣态,也可以避免 Java 语⾔的⼀些繁琐的写法,甚⾄后期还可以⽤上 Kotlin 的协程,来替代 Java ⽬前 的多线程并发模型。 当然,本⽂讨论的是 Web 应⽤架构,如果你的应⽤是数据处理⽅⾯,本⽂⽆法提供任何帮助。 ⽽在 Web 领域,Python 和 PHP 对⽐起 Node,其实并没有优势,所以就不考虑了。 WWeebb 框框架架如如何何选选 Node 的 Web 框架还挺多的,我⽐较熟悉的有: Sails.js ⼤⽽全,甚⾄有⾃⼰的 orm, node 世界的 ruby on rails Express 基础 web 框架 Koa 更基础的 web 框架,甚⾄两 body-parse 都不⾃带 Thinkjs Thinkphp 吧 Egg 阿⾥出品,挺受欢迎的 NestJS node 世界的 Spring MVC 我公司早期使⽤ sails,看中其集成的丰富功能,但是后期 sails 的成⻓速度跟不上社区,对⼀些新特性 Generator 等不能及时兼 容,⽽且太笨重了。 所以团队在 17 年左右转型选择了 Koa,然后⾃⼰组装实践各个基础功能,这个做法的好处是⾼度⾃定义,⾮常契合公司发展需 要。社社区区优优秀秀的的框框架架们们 在 17 年之后,相继涌现⼀些优秀框架,像 egg 和 nestjs,当时我们并没有采⽤他们。 Egg 本⾝是优秀的框架,但是它是为中台打造的,但我公司需要⽤ Node 完成本来是 Java 做的事情,我们会⽤ Node 做⽐较重的 业务,Egg 在这⽅便并不适合,例如没有模块划分的概念。 ⽐起 Egg ,NestJS 就更适合我们,当时看它的⽂档就能感受出来,NestJS 的作者们是真的拿这个框架来做后端业务的,⽂档⾥ 讲到了我们⽇常碰到的很多痛点: 模块划分 循环引⽤ OpenAPI
IOC 依赖注⼊ CRUD 当时感觉像是找到了真爱,但是实践过后,发现⼀些问题: 编码规范:NestJS 有⾃⼰的⼀套规范,⽽且它封装得⾜够多,基本上⽆法修改这套规范,只能妥协。 集成测试:我们在早期使⽤ Koa 的过程中,沉淀了⼀套集成测试⽅案,特别是对 mongodb,我们做了⼀些⼯具可以⾃动 注⼊和清除测试数据,要在 NestJs 实现这个功能,需要有⾜够的时间对其改造 基于上⾯两个原因,NestJS 并没有在团队推⼴开来。 ⾃⾃研研的的⽅⽅向向 社区的框架既然不合适,那就博采众⻓,⾃⼰组装⼀套框架出来,所以我最近捣⿎出了 @akajs, 预期说这个东西是框架,更准确 的说法是,Kalengo团队的Node后端开发最近实践集合。就是把后端常⽤的东西集合打包起来,包含: IOC 依赖注⼊ 注解式路由 CRUD 我们很多项⽬⾮常简单,重复的 CRUD ⼯作必须简化 Mongoose + Typescript 封装 Redis + Redlock 封装 集成测试⽀持 常⽤ Util,如⽇期数字处理等 请求参数校验和全局异常处理 此外还有些最佳实践 OpenAPI ⽂档⾃动⽣成 Docker ⽀持 UML 设计 模块划分 这些我们则通过项⽬模板来提供。 有了这套⼯具,后端可以更加轻量和统⼀,开发可以短时间内搭建起⼀个完备的后端服务。 五五、、总总结结 这套架构⽅式,不论是基础架构层⾯,还是应⽤层⾯,都是通过抽离通⽤的功能,把服务治理的东西抽出下沉到 Istio,把后端通 ⽤的⼯具和实践抽出放在 @akajs 和项⽬模板中,以此来简化上层应⽤。 这样⼀线的后端开发,可以专注于业务逻辑的实现,不必在为基础设施烦恼。毕竟⼩公司要⽣存,“快”是很重要的。 【END】 如果看到这⾥,说明你喜欢这篇⽂章,请转转发发、、点点赞赞。微信搜索「web_resource」,关注后回复「进群」或者扫描下⽅⼆维码 即可进⼊⽆⼴告交流群。 ↓扫扫描描⼆⼆维维码码进进群群↓↓
推荐阅读 11… Spring Boot:启动原理解析 22… ⼀键下载 Pornhub 视频! 33… Spring Boot 多模块项⽬实践(附打包⽅法) 44… ⼀个⼥⽣不主动联系你还有机会吗? 55… 团队开发中 Git 最佳实践 喜欢⽂章,点个在在看看
阅读原⽂ 声明:pdf仅供学习使⽤,⼀切版权归原创公众号所有;建议持续关注原创公众号获取最新⽂章,学习愉快!
阿⾥巴巴的技术专家,是如何画好架构图的? 阿⾥巴巴中间件 Java后端 2019-11-19 点击上⽅ Java后端,选择 设为星标 优质⽂章,及时送达 来源:阿⾥巴巴中间件 本⽂作者:三画,阿⾥巴巴技术专家。曾多年从事⼯作流引擎研发⼯作,现专注于⾼并发移动互联⽹应⽤的架构和开发,和本⽂ 贡献者均来⾃阿⾥巴巴零售通部⻔。 技术传播的价值,不仅仅体现在通过商业化产品和开源项⽬来缩短我们构建应⽤的路径,加速业务的上线速率,也体现在优秀⼯ 程师在⼯作效率提升、产品性能优化和⽤⼾体验改善等经验⽅⾯的分享,以提⾼我们的专业能⼒。 本⽂作者阿⾥巴巴技术专家三画,分享了⾃⼰和团队在画好架构图⽅⾯的理念和经验,⾸发于阿⾥内部技术分享平台,阿⾥巴巴 中间件授权转载,梓敬、鹏升和余乐对此⽂亦有贡献。 Tips:可以微信搜索:Java后端,关注后加⼊咱们⾃⼰的交流群。 当我们想⽤⼀张或⼏张图来描述我们的系统时,是不是经常遇到以下情况: 对着画布⽆从下⼿、删了⼜来? ⽤⼀张图描述我的系统,并且让产品、运营、开发都能看明⽩? 画了⼀半的图还不清楚受众是谁? 画出来的图到底是产品图功能图还是技术图⼜或是⼤杂烩? 图上的框框有点少是不是要找点⼉框框加进来? 布局怎么画都不满意…… 如果有同样的困惑,本⽂将介绍⼀种画图的⽅法论,来让架构图更清晰。 ## 先先厘厘清清⼀⼀些些基基础础概概念念 1、什么是架构 架构就是对系统中的实体以及实体之间的关系所进⾏的抽象描述,是⼀系列的决策。 架构是结构和愿景。 系统架构是概念的体现,是对物/信息的功能与形式元素之间的对应情况所做的分配,是对元素之间的关系以及元素同周边环境之 间的关系所做的定义。 做好架构是个复杂的任务,也是个很⼤的话题,本篇就不做深⼊了。有了架构之后,就需要让⼲系⼈理解、遵循相关决策。
2、什么是架构图 系统架构图是为了抽象的表⽰软件系统的整体轮廓和各个组件之间的相互关系和约束边界,以及软件系统的物理部署和软件系统 的演进⽅向的整体视图。 3、架构图的作⽤ ⼀图胜千⾔。要让⼲系⼈理解、遵循架构决策,就需要把架构信息传递出去。架构图就是⼀个很好的载体。那么,画架构图是为 了: 解决沟通障碍 达成共识 减少歧义 4、架构图分类 搜集了很多资料,分类有很多,有⼀种⽐较流⾏的是4+1视图,分别为场景视图、逻辑视图、物理视图、处理流程视图和开发视 图。场景视图 场景视图⽤于描述系统的参与者与功能⽤例间的关系,反映系统的最终需求和交互设计,通常由⽤例图表⽰。 逻辑视图
逻辑视图⽤于描述系统软件功能拆解后的组件关系,组件约束和边界,反映系统整体组成与系 统如何构建的过程,通常由UML的 组件图和类图来表⽰。 物理视图 物理视图⽤于描述系统软件到物理硬件的映射关系,反映出系统的组件是如何部署到⼀组可 计算机器节点上,⽤于指导软件系 统的部署实施过程。
处理流程视图 处理流程视图⽤于描述系统软件组件之间的通信时序,数据的输⼊输出,反映系统的功能流程 与数据流程,通常由时序图和流程图 表⽰。
开发视图 开发视图⽤于描述系统的模块划分和组成,以及细化到内部包的组成设计,服务于开发⼈员,反映系统开发实施过程。
以上 5 种架构视图从不同⻆度表⽰⼀个软件系统的不同特征,组合到⼀起作为架构蓝图描述系统架构。 ## 怎怎样样的的架架构构图图是是好好的的架架构构图图 上⾯的分类是前⼈的经验总结,图也是从⽹上摘来的,那么这些图画的好不好呢?是不是我们要依葫芦画瓢去画这样⼀些图? 先不去管这些图好不好,我们通过对这些图的分类以及作⽤,思考了⼀下,总结下来,我们认为,在画出⼀个好的架构图之前, ⾸先应该要明确其受众,再想清楚要给他们传递什么信息 ,所以,不要为了画⼀个物理视图去画物理视图,为了画⼀个逻辑视图 去画逻辑视图,⽽应该根据受众的不同,传递的信息的不同,⽤图准确地表达出来,最后的图可能就是在这样⼀些分类⾥。那 么,画出的图好不好的⼀个直接标准就是:受众有没有准确接收到想传递的信息。 明确这两点之后,从受众⻆度来说,⼀个好的架构图是不需要解释的,它应该是⾃描述的,并且要具备⼀致性和⾜够的准确性, 能够与代码相呼应。 ## 画画架架构构图图遇遇到到的的常常⻅⻅问问题题 1、⽅框代表什么?
为什么适⽤⽅框⽽不是圆形,它有什么特殊的含义吗?随意使⽤⽅框或者其它形状可能会引起混淆。 2、虚线、实线什么意思?箭头什么意思?颜⾊什么意思? 随意使⽤线条或者箭头可能会引起误会。 3、运⾏时与编译时冲突?层级冲突? 架构是⼀项复杂的⼯作,只使⽤单个图表来表⽰架构很容易造成莫名其妙的语义混乱。 ## 本本⽂⽂推推荐荐的的画画图图⽅⽅法法 C4 模型使⽤容器(应⽤程序、数据存储、微服务等)、组件和代码来描述⼀个软件系统的静态结构。这⼏种图⽐较容易画,也给 出了画图要点,但最关键的是,我们认为,它明确指出了每种图可能的受众以及意义。 下⾯的案例来⾃C4官⽹,然后加上了⼀些我们的理解,来看看如何更好的表达软件架构 1、语境图(System Context Diagram)
这是⼀个想象的待建设的互联⽹银⾏系统,它使⽤外部的⼤型机银⾏系统存取客⼾账⼾、交易信息,通过外部电邮系统给客⼾发 邮件。可以看到,⾮常简单、清晰,相信不需要解释,都看的明⽩,⾥⾯包含了需要建设的系统本⾝,系统的客⼾,和这个系统 有交互的周边系统。 ⽤途这样⼀个简单的图,可以告诉我们,要构建的系统是什么;它的⽤⼾是谁,谁会⽤它,它要如何融⼊已有的IT环境。这个图的受 众可以是开发团队的内部⼈员、外部的技术或⾮技术⼈员。即: 构建的系统是什么 谁会⽤它 如何融⼊已有的IT环境 怎么画 中间是⾃⼰的系统,周围是⽤⼾和其它与之相互作⽤的系统。这个图的关键就是梳理清楚待建设系统的⽤⼾和⾼层次的依赖,梳 理清楚了画下来只需要⼏分钟时间。 2、容器图(Container Diagram) 容器图是把语境图⾥待建设的系统做了⼀个展开。
上图中,除了⽤⼾和外围系统,要建设的系统包括⼀个基于java\spring mvc的web应⽤提供系统的功能⼊⼝,基于xamarin架构 的⼿机app提供⼿机端的功能⼊⼝,⼀个基于java的api应⽤提供服务,⼀个mysql数据库⽤于存储, 各个应⽤之间的交互都在箭头线上写明了。 看这张图的时候,不会去关注到图中是直⻆⽅框还是圆⻆⽅框,不会关注是实线箭头还是虚线箭头,甚⾄箭头的指向也没有引起 太多注意。 我们有许多的画图⽅式,都对框、线的含义做了定义,这就需要画图的⼈和看图的⼈都清晰的理解这些定义,才能读全图⾥的信 息,⽽现实是,这往往是⾮常⾼的⼀个要求,所以,很多图只能看个⼤概的含义。 ⽤途这个图的受众可以是团队内部或外部的开发⼈员,也可以是运维⼈员。⽤途可以罗列为: 展现了软件系统的整体形态 体现了⾼层次的技术决策 系统中的职责是如何分布的,容器间的是如何交互的 告诉开发者在哪⾥写代码 怎么画 ⽤⼀个框图来表⽰,内部可能包括名称、技术选择、职责,以及这些框图之间的交互,如果涉及外部系统,最好明确边界。
3、组件图(Component Diagram) 组件图是把某个容器进⾏展开,描述其内部的模块。 ⽤途这个图主要是给内部开发⼈员看的,怎么去做代码的组织和构建。其⽤途有: 描述了系统由哪些组件/服务组成 厘清了组件之间的关系和依赖 为软件开发如何分解交付提供了框架 4、类图(Code/Class Diagram)
这个图很显然是给技术⼈员看的,⽐较常⻅,就不详细介绍了。 ## 案案例例分分享享 下⾯是内部的⼀个实时数据⼯具的架构图。作为⼀个应该⾃描述的架构图,这⾥不多做解释了。如果有看不明⽩的,那肯定是还 画的不够好。 画好架构图可能有许多⽅法论,本篇主要介绍了C4这种⽅法,C4的理论也是不断进化的。但不论是哪种画图⽅法论,我们回到画 图初衷,更好的交流,我们在画的过程中不必被条条框框所限制。简⽽⾔之,画之前想好:画图给谁看,看什么,怎么样不解释
就看懂。 参考资料: C4官⽹: https://c4model.com/ 为什么需要软件架构图: https://www.infoq.cn/article/GhprrUlOYyOqS8*FR1pH 书籍:《程序员必读之软件架构》 声明:pdf仅供学习使⽤,⼀切版权归原创公众号所有;建议持续关注原创公众号获取最新⽂章,学习愉快!