hhhaze

hhhaze 查看完整档案

填写现居城市  |  填写毕业院校  |  填写所在公司/组织填写个人主网站
编辑

take(action);

个人动态

hhhaze 关注了专栏 · 2019-09-04

阿里巴巴云原生

关注云原生技术趋势,输出最优质云原生内容

关注 63

hhhaze 关注了专栏 · 2019-07-12

Go语言实战

收录实际项目中产生的文章,有趣有料,值得一读

关注 2443

hhhaze 收藏了文章 · 2019-04-03

Jaeger源码分析——服务注册与服务发现

原文:Jaeger源码分析——服务注册与服务发现

声明

 Jaeger官方并没有明确说明其服务注册和服务发现的具体使用和介绍,这部分功能是在分析源码的时候,发现其原理与服务注册和服务发现类似,所以结合自己对服务注册和服务发现的认识,做一次总结,有错还请指点。

TChannel服务注册和服务发现

image

 Jaeger不借助第三方工具也能实现服务注册和服务发现,这部分功能由其依赖的RPC框架提供。

第三方注册——手动注册

go run cmd/agent/main.go --collector.host-port=192.168.0.10:14267,192.168.0.11:14267
 在启动agent的时候,可配置多个collector静态地址,这部分地址会形成一张注册表。

注册表

  • 注册表结构
github.com/uber/tchannel-go/peer.go #59
type PeerList struct {
    sync.RWMutex
    parent          *RootPeerList
    //以hostPort为下标组成注册表
    peersByHostPort map[string]*peerScore
    //负载均衡实现
    peerHeap        *peerHeap
    scoreCalculator ScoreCalculator
    lastSelected    uint64
}
  • 健康检查
github.com/jaegertracing/jaeger/pkg/discovery/peerlistmgr/peer_list_mgr.go  #150
func (m *PeerListManager) ensureConnections() {
    peers := m.peers.Copy()
    minPeers := m.getMinPeers(peers)
    numConnected, notConnected := m.findConnected(peers)
    //有一定量的链接,就不进行健康检查
    if numConnected >= minPeers {
      return
    }
    ......
    for i := range notConnected {
      // swap current peer with random from the remaining positions
      r := i + m.rnd.Intn(len(notConnected)-i)
      notConnected[i], notConnected[r] = notConnected[r], notConnected[i]
      // try to connect to current peer (swapped)
      peer := notConnected[i]
      m.logger.Info("Trying to connect to peer", zap.String("host:port", peer.HostPort()))
      //用于控制超时
      ctx, cancel := context.WithTimeout(context.Background(), m.connCheckTimeout)
      conn, err := peer.GetConnection(ctx)
      cancel()
      if err != nil {
        m.logger.Error("Unable to connect", zap.String("host:port", peer.HostPort()), zap.Duration("connCheckTimeout", m.connCheckTimeout), zap.Error(err))
        continue
      }
      ......
    }
}
 在注册表上的地址,TChannel都会进行健康检查,每秒进行一次,如果0.25秒没有连接上,视为服务不可用。如果连接成功则保留当前服务实例,供agent提交数据使用。
github.com/uber/tchannel-go/connection.go #228
func (ch *Channel) newOutboundConnection(timeout time.Duration, hostPort string, events connectionEvents) (*Connection, error) {
    conn, err := net.DialTimeout("tcp", hostPort, timeout)
    if err != nil {
      if ne, ok := err.(net.Error); ok && ne.Timeout() {
        ch.log.WithFields(LogField{"hostPort", hostPort}, LogField{"timeout", timeout}).Infof("Outbound net.Dial timed out")
        err = ErrTimeout
      }
      return nil, err
    }

    return ch.newConnection(conn, hostPort, connectionWaitingToSendInitReq, events), nil
}

客户端服务发现

  • 软负载均衡
github.com/uber/tchannel-go/peer.go #149
func (l *PeerList) choosePeer(prevSelected map[string]struct{}, avoidHost bool) *Peer {
    var psPopList []*peerScore
    var ps *peerScore
    ......
    size := l.peerHeap.Len()
    for i := 0; i < size; i++ {
      //把peer从Heap头部弹出来
      popped := l.peerHeap.popPeer()
      if canChoosePeer(popped.HostPort()) {
          ps = popped
          break
      }
      psPopList = append(psPopList, popped)
    }
    //不符合的放入Heap尾部
    for _, p := range psPopList {
        heap.Push(l.peerHeap, p)
    }

    if ps == nil {
        return nil
    }
    //符合条件的打分,再放入Heap尾部
    l.peerHeap.pushPeer(ps)
    ps.chosenCount.Inc()
    return ps.Peer
}
 当Agent需要提交数据的时候,会从TChannel的负载均衡获取peer(服务信息),当有多个的时候,TChannel通过轮询方式,查询peer。实现方式:注册表把所有peer放入peerHeap,先把peer从头部弹出,再把peer放回尾部,从而实现轮询策略的负载均衡。
  • 重试
github.com/uber/tchannel-go/retry.go #212
func (ch *Channel) RunWithRetry(runCtx context.Context, f RetriableFunc) error {
    var err error

    opts := getRetryOptions(runCtx)
    rs := ch.getRequestState(opts)
    defer requestStatePool.Put(rs)
    //默认重试5次
    for i := 0; i < opts.MaxAttempts; i++ {
        rs.Attempt++

        if opts.TimeoutPerAttempt == 0 {
            err = f(runCtx, rs)
        } else {
            attemptCtx, cancel := context.WithTimeout(runCtx, opts.TimeoutPerAttempt)
            err = f(attemptCtx, rs)
            cancel()
        }

        if err == nil {
            return nil
        }
        if !opts.RetryOn.CanRetry(err) {
            if ch.log.Enabled(LogLevelInfo) {
                ch.log.WithFields(ErrField(err)).Info("Failed after non-retriable error.")
            }
          return err
        }
        ......
    }

    // Too many retries, return the last error
    return err
}
 网络之间的通讯避免不了网络异常,所以为了提高可用性,重试是其中一种方式。当从负载均衡获取peer提交数据到Collector,如果提交失败,会再从负载均衡获取peer,最多5次,如果5次都不成功就会放弃这次提交。

Consul+docker 服务注册和服务发现

image

 使用consul实现服务注册和服务发现是一件很简单的事情。很多功能都是开箱即用。

准备工作

  • 启动Consul——ip:172.18.0.2
docker run -itd --network=backend \
-p 8400:8400 -p 8500:8500 -p 8600:53/udp \
-h node1 progrium/consul -server -bootstrap -ui-dir /ui
  • 启动Agent
docker run \
-itd --network=backend \
--name=jaeger-agent \
-p5775:5775/udp \
-p6831:6831/udp \
-p6832:6832/udp \
-p5778:5778/tcp \
--dns-search="service.consul" --dns=172.18.0.2 \
jaegertracing/jaeger-agent \
/go/bin/agent-linux --collector.host-port=jaeger-collector:14267
  • 启动Collector
#node1
docker run -itd --network=backend \
--name=jaeger-collector-node1 \
-p :14267 \
--dns-search="service.consul" --dns=172.18.0.2 \
jaegertracing/jaeger-collector \
/go/bin/collector-linux \
--span-storage.type=cassandra \
--cassandra.keyspace=jaeger_v1_dc \
--cassandra.servers=cassandra:9042

#node2
docker run -itd --network=backend \
--name=jaeger-collector-node2 \
-p :14267 \
--dns-search="service.consul" --dns=172.18.0.2 \
jaegertracing/jaeger-collector \
/go/bin/collector-linux \
--span-storage.type=cassandra \
--cassandra.keyspace=jaeger_v1_dc \
--cassandra.servers=cassandra:9042

服务注册——自动注册

docker run -itd --net=backend --name=registrator \
--volume=/var/run/docker.sock:/tmp/docker.sock \
gliderlabs/registrator:latest \
consul://172.18.0.2:8500
 使用consul+docker的形式,只要部署好服务,就会被自动注册到consul,十分简单。

注册表

  • 查看注册表信息
查看注册表信息http://localhost:8500/ui/#/dc1/nodes/node1

image

 可以看到启动的2个Collector服务ip分别为:172.18.0.5和172.18.0.8
 consul提供了很多种健康检查方式:HTTP、TCP、Docker、Shell和TTL。详情可以查看官网。

服务端服务发现

 Consul相对于Agent和Collector是远程服务,所以提供了2种服务发现方式:HTTP和DNS,在这里主要使用是DNS,因为简单,轻量。
  • DNS和软负载均衡
 当Agent通过DNS解析出多个IP的时候,Consul会随机选择一个IP给Agent实现负载均衡。

 由于DNS存在缓存,所以有可能出现,服务不健康,一样会被正常解析,所以在默认情况下Consul是没有设置缓存时间,TTL为0,但是也考虑到了不缓存对Consul的压力,所以开放配置,让我们去决定缓存时间点DNS Caching

总结

TChannel与Consul+docker实现的服务发现和服务注册中都有他们的优缺点:

服务注册

  • TChannel
 TChannel的服务注册适用于一些基础服务,例如Jaeger就属于一种基础服务,这种服务一旦部署很少会变动。
  • Consul + docker
 在现在docker流行的大环境下使用Consul实现的服务注册会简单很多,docker有一个特点就是ip地址是动态,所以它很适合业务场景,因为业务经常变动,服务也随着变化。

健康检查

 TChannel和Consul都提供了健康检查,但是都只是检测服务是否正在运行,无法了解是否能够正常处理请求。

服务发现

  • TChannel
 TChannel使用的是客户端服务发现,这种方式相对于Consul的服务端服务发现的优点就是没有了远程网络开销,单点问题。同时缺点就是各个语言都需要自己实现注册表,负载均衡等功能。
  • Consul
 Consul使用服务端服务发现,它可以很好的和其他服务结合使用,不需要关心注册表,负载均衡等。而且关于网络开销和单点问题都提供了方案。
查看原文

hhhaze 收藏了文章 · 2019-01-15

go依赖管理-govendor

Golang 官方并没有推荐最佳的包管理方案。到了1.5版本时代,官方引入包管理的设计,加了 vendor 目录来支持本地包管理依赖。官方 wiki 推荐了多种支持这种特性的包管理工具,如:Godep、gv、gvt、glide、govendor等。

下面简要介绍一个我在项目中用到的 -- govendor
该工具将项目依赖的外部包拷贝到项目下的 vendor 目录下,并通过 vendor.json 文件来记录依赖包的版本,方便用户使用相对稳定的依赖。
对于 govendor 来说,依赖包主要有以下多种类型:

状态缩写状态含义
+locall本地包,即项目自身的包组织
+externale外部包,即被 $GOPATH 管理,但不在 vendor 目录下
+vendorv已被 govendor 管理,即在 vendor 目录下
+stds标准库中的包
+unusedu未使用的包,即包在 vendor 目录下,但项目并没有用到
+missingm代码引用了依赖包,但该包并没有找到
+programp主程序包,意味着可以编译为执行文件
+outside外部包和缺失的包
+all所有的包

Installation

go get -u github.com/kardianos/govendor

命令行执行 govendor,若出现以下信息,则说明安装成功。

➜  ~ govendor
govendor (v1.0.8): record dependencies and copy into vendor folder
    -govendor-licenses    Show govendor's licenses.
    -version              Show govendor version
...
...

Warning: 需要把 $GOPATH/bin/ 加到 PATH 中。

Quickstart

# Setup your project.
cd "my project in GOPATH"
# 初始化 vendor 目录, project 下出现 vendor 目录
govendor init

# Add existing GOPATH files to vendor.
govendor add +external

# View your work.
govendor list

# Look at what is using a package
govendor list -v fmt

# Specify a specific version or revision to fetch
govendor fetch golang.org/x/net/context@a4bbce9fcae005b22ae5443f6af064d80a6f5a55

# Get latest v1.*.* tag or branch.
govendor fetch golang.org/x/net/context@v1   

# Get the tag or branch named "v1".
govendor fetch golang.org/x/net/context@=v1  

# Update a package to latest, given any prior version constraint
govendor fetch golang.org/x/net/context

# Format your repository only
govendor fmt +local

# Build everything in your repository only
govendor install +local

# Test your repository only
govendor test +local

Sub-commands

init     创建 vendor 文件夹和 vendor.json 文件
list     列出已经存在的依赖包
add      从 $GOPATH 中添加依赖包,会加到 vendor.json
update   从 $GOPATH 升级依赖包
remove   从 vendor 文件夹删除依赖
status   列出本地丢失的、过期的和修改的package
fetch   从远端库增加新的,或者更新 vendor 文件中的依赖包
sync     Pull packages into vendor folder from remote repository with revisions
migrate  Move packages from a legacy tool to the vendor folder with metadata.
get     类似 go get,但是会把依赖包拷贝到 vendor 目录
license  List discovered licenses for the given status or import paths.
shell    Run a "shell" to make multiple sub-commands more efficient for large projects.

go tool commands that are wrapped:
      `+<status>` package selection may be used with them
    fmt, build, install, clean, test, vet, generate, tool

warning

  • The project must be within a $GOPATH/src.

  • If using go1.5, ensure you set GO15VENDOREXPERIMENT=1.

参考

govendor github
go 依赖管理利器 -- govendor

查看原文

hhhaze 评论了文章 · 2018-12-29

禁用CleanMyMac HealthMonitor

介绍CleanMyMac

大家都知道,在Windows系统上,有360等软件做垃圾清理和软件卸载,对于大多数Mac用户,相对来说不需要类似软件做清理工作,然而,某些强迫症患者或者码农依然需要清理软件,CleanMyMac就是这么一款Mac平台上最好用的清理软件

牛皮癣后台服务

完整退出 CleanMyMac X 的程序包括菜单栏启动项后,依然有一个服务HealthMonitor在后台运行着,而且占用大量内存

即使你在安装目录删除该软件,只要你启动过CleanMyMac,这个服务又会生成,并持续后台运行

证据确凿

下面笔者介绍如何禁用该服务,还你一片清净

vim修改启动配置文件

sudo vim /private/var/db/launchd.db/com.apple.launchd/overrides.plist

加入如下代码

包名(com.macpaw.CleanMyMac4.HealthMonitor)随着CleanMyMac版本不同会有改动

<key>com.macpaw.CleanMyMac4.HealthMonitor</key>
        <dict>
                <key>Disabled</key>
                <true/>
        </dict>

可以通过CleanMyMac查看包名
CleanMyMac查看包名

参考资料

本文为原创文章,转载注明出处,欢迎扫码关注公众号 楼兰 或者网站https://lovecoding.club,第一时间看后续精彩文章,觉得好的话,顺手分享到朋友圈吧,感谢支持。

image

查看原文

hhhaze 收藏了文章 · 2018-12-21

MySQL性能管理及架构设计(三):SQL查询优化、分库分表 - 完结篇

上一篇:MySQL性能管理及架构设计(二):数据库结构优化、高可用架构设计、数据库索引优化

一、SQL查询优化(重要

1.1 获取有性能问题SQL的三种方式

  1. 通过用户反馈获取存在性能问题的SQL;
  2. 通过慢查日志获取存在性能问题的SQL;
  3. 实时获取存在性能问题的SQL;

1.1.2 慢查日志分析工具

相关配置参数:

slow_query_log # 启动停止记录慢查日志,慢查询日志默认是没有开启的可以在配置文件中开启(on)
slow_query_log_file # 指定慢查日志的存储路径及文件,日志存储和数据从存储应该分开存储

long_query_time # 指定记录慢查询日志SQL执行时间的阀值默认值为10秒通常,对于一个繁忙的系统来说,改为0.001秒(1毫秒)比较合适
log_queries_not_using_indexes #是否记录未使用索引的SQL

  常用工具:mysqldumpslowpt-query-digest

pt-query-digest --explain h=127.0.0.1,u=root,p=p@ssWord  slow-mysql.log

1.1.3 实时获取有性能问题的SQL(推荐

clipboard.png

SELECT id,user,host,DB,command,time,state,info
FROM information_schema.processlist
WHERE TIME>=60

  查询当前服务器执行超过60sSQL,可以通过脚本周期性的来执行这条SQL,就能查出有问题的SQL

1.2 SQL的解析预处理及生成执行计划(重要

1.2.1 查询过程描述(重点!!!

clipboard.png

上图原文连接

通过上图可以清晰的了解到MySql查询执行的大致过程:

  1. 发送SQL语句。
  2. 查询缓存,如果命中缓存直接返回结果。
  3. SQL解析,预处理,再由优化器生成对应的查询执行计划。
  4. 执行查询,调用存储引擎API获取数据。
  5. 返回结果。

1.2.2 查询缓存对性能的影响(建议关闭缓存

第一阶段:
相关配置参数:

query_cache_type # 设置查询缓存是否可用
query_cache_size # 设置查询缓存的内存大小
query_cache_limit # 设置查询缓存可用的存储最大值(加上sql_no_cache可以提高效率)
query_cache_wlock_invalidate # 设置数据表被锁后是否返回缓存中的数据
query_cache_min_res_unit # 设置查询缓存分配的内存块的最小单
缓存查找是利用对大小写敏感的哈希查找来实现的,Hash查找只能进行全值查找(sql完全一致),如果缓存命中,检查用户权限,如果权限允许,直接返回,查询不被解析,也不会生成查询计划。

在一个读写比较频繁的系统中,建议关闭缓存,因为缓存更新会加锁。将query_cache_type设置为off,query_cache_size设置为0

1.2.3 第二阶段:MySQL依照执行计划和存储引擎进行交互

  这个阶段包括了多个子过程:

  clipboard.png

  clipboard.png

  clipboard.png

一条查询可以有多种查询方式,查询优化器会对每一种查询方式的(存储引擎)统计信息进行比较,找到成本最低的查询方式,这也就是索引不能太多的原因

1.3 会造成MySQL生成错误的执行计划的原因

    1、统计信息不准确
    2、成本估算与实际的执行计划成本不同

    clipboard.png

   3、给出的最优执行计划与估计的不同

    clipboard.png

    4、MySQL不考虑并发查询
    5、会基于固定规则生成执行计划
    6、MySQL不考虑不受其控制的成本,如存储过程,用户自定义函数

1.4 MySQL优化器可优化的SQL类型

查询优化器:对查询进行优化并查询mysql认为的成本最低的执行计划。 为了生成最优的执行计划,查询优化器会对一些查询进行改写

  可以优化的sql类型

  1、重新定义表的关联顺序;

  clipboard.png

  2、将外连接转换为内连接;

  3、使用等价变换规则;

  clipboard.png

  4、优化count(),min(),max();

  clipboard.png

  5、将一个表达式转换为常数;
  6、子查询优化;

  clipboard.png

  7、提前终止查询,如发现一个不成立条件(如where id = -1),立即返回一个空结果;

  8、对in()条件进行优化;

1.5 查询处理各个阶段所需要的时间

1.5.1 使用profile(目前已经不推荐使用了)

set profiling = 1; #启动profile,这是一个session级的配制执行查询

show profiles; # 查询每一个查询所消耗的总时间的信息

show profiles for query N; # 查询的每个阶段所消耗的时间

1.5.2 performance_schema是5.5引入的一个性能分析引擎(5.5版本时期开销比较大)

启动监控和历史记录表:use performance_schema

update setup_instruments set enabled='YES',TIME = 'YES' WHERE NAME LIKE 'stage%';

update set_consumbers set enabled='YES',TIME = 'YES' WHERE NAME LIKE 'event%';

    clipboard.png

    clipboard.png

1.6 特定SQL的查询优化

1.6.1 大表的数据修改

    clipboard.png

    clipboard.png

1.6.2 大表的结构修改

    clipboard.png

  1. 利用主从复制,先对从服务器进入修改,然后主从切换
  2. (推荐)
添加一个新表(修改后的结构),老表数据导入新表,老表建立触发器,修改数据同步到新表, 老表加一个排它锁(重命名), 新表重命名, 删除老表。

    clipboard.png

修改语句这个样子:

alter table sbtest4 modify c varchar(150) not null default ''

利用工具修改:

    clipboard.png

1.6.3 优化not in 和 <> 查询

  子查询改写为关联查询:

  clipboard.png

二、分库分表

2.1 分库分表的几种方式

分担读负载 可通过 一主多从,升级硬件来解决。

2.1.1 把一个实例中的多个数据库拆分到不同实例(集群)

clipboard.png

    拆分简单,不允许跨库。但并不能减少写负载。

2.1.2 把一个库中的表分离到不同的数据库中

clipboard.png

    该方式只能在一定时间内减少写压力。

    以上两种方式只能暂时解决读写性能问题。

2.1.3 数据库分片

对一个库中的相关表进行水平拆分到不同实例的数据库中

clipboard.png

2.1.3.1 如何选择分区键

  1. 分区键要能尽可能避免跨分区查询的发生
  2. 分区键要尽可能使各个分区中的数据平均

2.1.3.2 分片中如何生成全局唯一ID

    clipboard.png

扩展:表的垂直拆分和水平拆分

完!

参考教学视频:实战MySQL性能管理及架构设计

查看原文

hhhaze 评论了文章 · 2018-12-17

基于go搭建微服务实践教程 (一)

概览


下面这张图就是我们将要搭建的项目的概览图。我们将开始写第一个微服务之后我们会一点点完成这张图的所有内容。

clipboard.png

讲解:
白色虚线的方框内: docker swarm集群,运行在一个或多个节点上。
蓝色方框内: Spring cloud/Netflix OSS提供的支持系统,或者其他服务,比如zipkin
黄色方框/白色方框: 一个微服务。

运行资源消耗


为什么我们要用go来写微服务?除啦有意思和有效率,另一个主要原因是go在运行时消耗的内存非常小。下面这张图对比了Spring boot,Spring cloud和go.(都是基于运行在docker swarm上的微服务)

clipboard.png

quotes-service是基于spring boot.compservice和accountservice是基于go.两个都是基于http服务器并且有许多库来和spring cloud集成。

但是内存消耗现在还要考虑么?我们现在不是有GB级别的RAM能轻松负载一个java应用么?
可以这么说,但是对于一个大企业,他们不止运行几十个微服务,甚者成百上千的微服务。这样的话减少资源消耗就能省下很多钱。

来看一下亚马逊主机的价格:

clipboard.png

第二列为cpu数量,第四列为RAM大小。
我们看到当RAM增加一倍,价钱也随着增加一倍。如果你的cpu足够,就不必为缺少内存而要多花一倍的钱,后面我们也会看到go的服务在处理请求时甚至比spring boot 在闲置的时候少。

微服务非功能性要求


这篇博客不仅仅关于go搭建微服务,更是如何在spring cloud环境下运行和搭建符合真正用于生产环境的微服务产品。包括:

  • 中心化配置
  • 服务发现
  • 日志
  • 分布式探寻
  • 熔断
  • 负载均衡
  • 边缘
  • 监测
  • 安全

这些内容都应该在为服务里实现,不仅仅是用go,其他的语言,例如,java,python,写微服务产品时也应该实现这些功能.在这篇博客中我会尽量从go语言角度覆盖这些内容。

另一方面要考虑的是关于微服务的实现,你可能听过:

  • HTTP/RPC/REST/SOAP/任何形式的APIS
  • 持久化数据的API (DB clients, JDBC, O/R mappers)
  • 消息处理API (MQTT, AMQP, JMS)
  • 测试 (单元测试,integration, system, acceptance test)
  • 编译工具/CI/CD

我会讲解这其中的一些。

在docker swarm上运行

概览中我们看到我们的服务会运行在docker swarm中,这意味着我们所有的服务,包括支持服务(服务器配置,边缘等)和微服务程序都会运行在docker swarm中。在这个项目结束时,我们运行:

docker service ls

我们会看到下面这些服务

![图片上传中...]

注意:上面这些服务远多于我们在第五章里搭建的s
clipboard.png

go微服务占用很小的内存-但是性能怎么样?对编程语言做有意义的基测很难。从基测网站上提交的算法上看,go比java8大部分时间会快。go大部分情况和c++差不多快,但在一些基测上,要慢很多。就是说,对于普通的微服务工作-负载HTTP/RPC,序列化/反序列化数据结构,网络吞吐方面,go可以表现的不错。

另一个重要的特性是go具有垃圾回收功能,在go 1.5GC的垃圾回收中只需要停顿几微秒。go的垃圾回收也许不是那么成熟,但是在1.2之后,他表现的很稳定。更为惊奇的是,你可以更改通过更改整个栈相对于类的大小,来更改垃圾回收的性能。

然而,我们在测试性能的同时会写我们的第一个微服务,之后我们会加入熔断,追踪,日志等功能。在我们加入越来越多的功能之后,我们会用Gatling来测试我们的性能。

启动时间


另一个go的特性是它的启动速度非常快。一个普通的HTTP服务器加上一些路由和json序列化功能的服务在几百毫秒就可以启动。当我们在docker中运行时,我们可以运行它们在几秒之内。然而,一个spring boot的微服务需要至少10秒。这个看起来好像没什么影响,但是在你需要快速应对大流量的处理时将会非常有用。

静态链接二进制


另一个优点go的静态链接的二进制包含所有的依赖在一个可执行的二进制文件中,我们可以把这个包放在docker容器中。同时这个文件不大,一般来说10-20MB。这样我们就能得到一个很简单的dokerfile.我们可以用一个很基本的docker镜像来开始。我用的是 iron/base,这个镜像大约6MB。

FROM iron/base
EXPOSE 6868
ADD eventservice-linux-amd64 \
ENTRYPOINT ["./eventservice-linux-amd64", "-profile=test"]

换句话说,不需要JVM或者其他运行的组件,只需要这个镜像里标准的c库
我们之后会讲解如何编译二进制文件和-profile=test是什么意思。

总结


这篇博客中,我们介绍为什么要用go来做微服务。主要原因是:

  • 小的内存消耗
  • 性能良好
  • 静态链接二进制文件的方便

下一篇文章中,我们会开始写第一个微服务。

查看原文

hhhaze 关注了专栏 · 2018-02-23

FinanceR

循环写作,持续更新,形成闭环,贵在坚持

关注 1017

hhhaze 赞了文章 · 2017-06-26

全站缓存时代

原则:动静分离,分级缓存,主动失效。

Web 开发中,接口会被分为以下几类:

  1. 纯静态页面。打死我都不会修改的页面。很长一段时间内,基本上不会修改。比如:关于我们。

  2. 纯动态页面。实时性,个性化要求比较高。页面变化很大,或者每个用户看到的都不一样,比如:朋友圈。

  3. 短时静态页面。在一定时间内基本不会变化,或者是容忍不需要实时更新。比如:文章、新闻。

  4. 动静结合页面。这个页面既有动态,也有静态内容。也是实际应用中最多的。

对于以上类型的页面,可以做不同的缓存方案。各位大神们应该根据自己业务的情况,灵活调整缓存方案。以下内容可以作为参考。

模板渲染

高速发展的模板引擎,给前端渲染带来了活力。Mustache、jade、hbs 灵活的模板语法让页面开发变得更省力和高效。

HtmlDOM == VeiwEngine.render(template ,data);

浏览器只认识 DOM 结构的字符串,也就是常说的 HTML5 格式。对于前端来渲染 DOM,还是后端渲染的问题,在此不用讨论,为了情况前端的性能和体验,后端渲染会更合适。对于同一个页面,每次请求都会产生一次渲染吗?渲染总是要计算的,这样多浪费服务器性能啊!确实是这样,除非你用了缓存。

页面缓存的方案

1. 纯静态页面

直接放 CDN。纯静态页面的访问量一般不会很大,程序直接响应也是可以的。

2. 纯动态页面

都说是动态页面了,那就不要做页面缓存了。可以考虑做数据缓存,或者是 redis、DB 缓存。

3. 短时静态页面

1. 服务器端文件缓存

请求-->处理接口--> 模板渲染 ---> 存储文件---> 响应文件

缓存动态页面,你也可以把生成的文件存到 CDN,然后让 CDN 去响应请求。如果你的请求需要过一些验证,那就把文件存储到服务器,由业务服务器去响应请求。文件还有一个好处是:流。例如:FileReadStream.pipe(ResponseStream)。响应的时候,不需要把文件的内容加载到内存,而是直接用 stream 的方式响应。但是弊端也不少,文件存储,会有并发读写死锁问题。

还有一个问题,分布式系统。可能你有 A、B、C 三个服务器。A 服务器生成了一个文件,还需要实时同步到 B 和 C。当然也可以让 A、B、C 挂载同一个磁盘。问题又来了,这个文件要不要备份呢?

2. Redis Cache

请求--> 接口接口---> 模板渲染 --> 存储数据--> 响应 DOM

把请求的 url 当做 key,把模板渲染好的数据当做值,然后根据缓存规则,把数据存储到 redis。

这种小成本的缓存在我们的系统中有实践,的确大幅提高了系统的响应时间和 QPS,页面的请求大部分是从 redis 读数据,然后返回,单机测试过极限性能,14k QPS。简单描述一下。我们称之为静态化staticize

  1. 开始请求

  2. 请求校验,filter 等等

  3. 查询缓存 redis

  4. 如果有缓存,则直接响应

  5. 没有缓存,查询数据,重新渲染,存储到 redis.

  6. 响应

  7. 如果需更新缓存,只需要删掉对应的redis 值

4. 动静结合的页面

这种页面在实际情况中更常见。原则:静态页面缓存,动态部分异步请求。

动静结合

静态部分也是模板渲染过来的,浏览器会从 CDN 或者后台缓存中获取到静态页面。页面响应的时间和浏览器的渲染会直接影响用户体验。动态更新的部分一般会在一些细节部分,比如页面的登录状态。对于所有用户来说,我看到的这个页面,只有用户头像部分会不一致。如果系统为每个用户生成一个静态页面成本就太高了,而且完全没必要。

这个页面就变成了:页面 == 短时静态页面 + 局部动态页面。

『用户状态信息』这个特殊的动态内容,还需要用到本地的缓存机制。用户在切换页面的时候,每个页面都需要动态加载用户信息,所以我们的做法是在第一次请求到这个信息的时候,存储到 localStorage,然后设置过期时间。退出的时候,主动清理 localStorage。

比如:个性化,个人推荐这种因人而异的板块都可以做成局部动态页面的形式。

5. 数据缓存

以上的方案同样适用于异步请求。

对于CDN 或者其他缓存来说,缓存不知道你存的内容是 DOM 还是 JSON,还是其他格式。它只是帮你存储数据。你同样可以的把,数据接口、局部 DOM 结构(非完整 html 格式)存储到 CDN 或者是 redis 中。比如:页面的配置信息,或者从相关推荐系统请求的 dom 结构。

缓存更新

一般会有主动失效和自动失效缓存机制。

CDN 和 redis 等缓存都可以根据规则设置缓存时间。缓存过期后,会再次获取新的数据。
主动更新一般会用 API 调用方式实现。比如删除 key,或者调用 CDN 接口进行删除操作

clipboard.png

缓存穿透

一般会在第一次请求的时候生成缓存,如果服务器端没有缓存,然后在同一时刻出现高并发请求,请求会直接到达业务逻辑部分,很可能导致系统直接挂掉。

解决办法:

  1. 主动创建缓存。缓存求由系统定时创建。

  2. 请求的时候设置标志位。第一个请求到达,标识这个 url 正在创建缓存,其他请求进入等待队列。

全站 CDN 加速

CDN 动态加速如下图所示:

clipboard.png

例如我的网站有以下接口和页面:

  1. http://www.localhost.com/ // 短时缓存,动静结合

  2. http://www.localhost.com/api/user/1 // 纯动态

  3. http://www.localhost.com/post/hello-world // 永久静态

所以,1、3页面会放到 CDN,2 直接去源站请求。怎么做到呢?

  1. 在 CDN 配置自主源站。意味着请求 CDN 地址的时候,CDN 会去源站请求数据,然后缓存到 CDN 节点。

  2. 设置缓存规则

    / 缓存 1 分钟
    /post/* 缓存 1 年
    /api/ 不设置缓存
  3. cname www.localhost.com 到 CDN 提供的空间域名

clipboard.png

多平台 Mulit Origin

一个 URL 可能会在不同的平台有不同的返回和表现形式。

产品的想法都是很完美,一个按钮在不同的平台会有不同的显示状态。实际情况非常复杂,在我们的系统中,出现过一个页面出现在 七 个平台,每个平台的显示效果会不一致。不管是模板渲染,或者是 js 处理按钮状态等等都是非常复杂的,或者 pc 和移动端页面表现出样式和结构差异。如果还要把这个页面放到缓存,就更加复杂了。

为每个平台生成一份缓存?可以!

平台的识别来自 UserAgent,不同的浏览器或者 app,都有不同的UserAgent。不同的来源我们称之为 Origin。Origin + url 就可以生成唯一的 key,去识别唯一的缓存。缓存不限于 redis 和 文件缓存。

CDN 识别来源去读取不同的文件,就需要 CDN 那边做一些开发工作了。Upyun、七牛这边暂时不支持的。BAT这种大公司他们自己维护的 CDN 就能完美地做到。

另一种思路:

1个项目,两个域名,2个动态 CDN。PC 和移动端页面分离、接口共享。

例如:为同一个项目配置两个域名:www.localhost.comm.www.localhost.com,同时为这两个域名各设置一个动态 CDN。

由一项目提供两个域名服务,比如:IndexController.main 处理请求 /homepage,移动端和 PC 端的请求路径分别为

  • http://m.www.localhost.com/homepage

  • http://www.localhost.com/homepage

main action 会根据请求来源url,分别渲染不同的页面。不同的域名页面,也就被不同的动态 CDN 缓存起来。

对于 /api/xxxx的接口,自然不需要做 PC 和移动端或者其他平台的区分,一个 action 就可以解决了。这样就避免了维护两套系统的问题。

结语

以上,全站缓存基本完成。

不要凭空去拉高 QPS或者乱用缓存,根据你的业务和实际情况来对待。最重要的事情就是要牢记:保持简洁,按需使用。

参考文献

查看原文

赞 26 收藏 125 评论 6

hhhaze 回答了问题 · 2017-01-06

解决如何使用php.exe来执行php文件

require使用绝对路径

关注 5 回答 3

认证与成就

  • 获得 6 次点赞
  • 获得 2 枚徽章 获得 0 枚金徽章, 获得 1 枚银徽章, 获得 1 枚铜徽章

擅长技能
编辑

(゚∀゚ )
暂时没有

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2012-02-29
个人主页被 577 人浏览