<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">

  <title><![CDATA[red]]></title>
  <link href="tongzhao.red/atom.xml" rel="self"/>
  <link href="tongzhao.red/"/>
  <updated>2019-12-20T10:36:34+08:00</updated>
  <id>tongzhao.red/</id>
  <author>
    <name><![CDATA[]]></name>
    
  </author>
  <generator uri="http://www.mweb.im/">MWeb</generator>
  
  <entry>
    <title type="html"><![CDATA[go-kit 服务发现(4) Eureka]]></title>
    <link href="tongzhao.red/15612106833607.html"/>
    <updated>2019-06-22T21:38:03+08:00</updated>
    <id>tongzhao.red/15612106833607.html</id>
    <content type="html"><![CDATA[
<h2 id="toc_0">基本使用</h2>

<h3 id="toc_1">注册</h3>

<pre><code class="language-go">logger := log.NewLogfmtLogger(os.Stdout)
var fargoConfig fargo.Config
fargoConfig.Eureka.ServiceUrls = []string{&quot;http://localhost:8761/eureka&quot;}
// 订阅服务器应轮询更新的频率。
fargoConfig.Eureka.PollIntervalSeconds = 1

instance := &amp;fargo.Instance{
    InstanceId : &quot;实例ID&quot;,
    //HostName:         &quot;127.0.0.1&quot;,
    Port:   8080,
    App:    &quot;hello&quot;,
    IPAddr: &quot;http://127.0.0.1&quot;,
    //HealthCheckUrl: &quot;http://localhost:8080/hello&quot;,
    //StatusPageUrl:  &quot;http://localhost:8080/hello&quot;,
    //HomePageUrl:    &quot;http://localhost:8080/hello&quot;,
    Status:         fargo.UP,
    DataCenterInfo: fargo.DataCenterInfo{Name: fargo.MyOwn},
    LeaseInfo:      fargo.LeaseInfo{RenewalIntervalInSecs: 1},
}
fargoConnection := fargo.NewConnFromConfig(fargoConfig)
register := eureka.NewRegistrar(&amp;fargoConnection, instance, logger)

register.Register()
defer register.Deregister()
</code></pre>

<h3 id="toc_2">发现</h3>

<pre><code class="language-go">logger := log.NewLogfmtLogger(os.Stdout)
var fargoConfig fargo.Config
fargoConfig.Eureka.ServiceUrls = []string{&quot;http://localhost:8761/eureka&quot;}
fargoConfig.Eureka.PollIntervalSeconds = 1

fargoConnection := fargo.NewConnFromConfig(fargoConfig)
instancer := eureka.NewInstancer(&amp;fargoConnection,&quot;hello&quot;,logger)
</code></pre>

<h2 id="toc_3">底层原理</h2>

<h3 id="toc_4">目录结构</h3>

<pre><code>.
├── doc.go
├── instancer.go 服务实例 
├── instancer_test.go
├── integration_test.go
├── registrar.go 注册器
├── registrar_test.go
└── util_test.go

</code></pre>

<p>目录中主要的是这三个文件，<strong>instancer.go</strong> <strong>registrar.go</strong><br/>
与sd目录下的其他注册发现组件不同，<a href="https://github.com/go-kit/kit/commit/443f6ead51a91ddf48136543c1878aedb3efb2fd#diff-1bc8b1643e279c1d7764b3ac84134584">作者删除了client.go文件</a>，改为使用<a href="https://github.com/hudl/fargo/blob/master/struct.go#L18">fargo.EurekaConnection</a></p>

<h3 id="toc_5">registrar.go</h3>

<pre><code class="language-go">// Registrar maintains service instance liveness information in Eureka.
type Registrar struct {
    conn     fargoConnection
    instance *fargo.Instance
    logger   log.Logger
    quitc    chan chan struct{}
    sync.Mutex
}

func NewRegistrar(conn fargoConnection, instance *fargo.Instance, logger log.Logger) *Registrar
func (r *Registrar) Register()
func (r *Registrar) Deregister()
func (r *Registrar) loop()
func (r *Registrar) heartbeat() error 
</code></pre>

<p>包含以下3个函数</p>

<ul>
<li><strong>NewRegistrar</strong> 创建 Registrar</li>
<li><strong>Register</strong> 通过 fargoConnection 注册服务</li>
<li><strong>Deregister</strong> 通过 fargoConnection 注销服务</li>
<li><p><strong>loop</strong></p>

<ul>
<li><code>fargoConfig.Eureka.PollIntervalSeconds</code> 设置的定时器，每隔设定的秒数(默认30秒)，调用<strong>heartbeat</strong>函数</li>
<li>监听<code>Registrar.quitc</code>,退出loop</li>
</ul></li>
<li><p><strong>heartbeat</strong></p>

<ul>
<li>调用<code>fargo.EurekaConnection.HeartBeatInstance</code>，向Eureka中注册的服务实例发送心跳请求，具体的发送方式为向指定的节点发送Http PUT请求，如<code>PUT http://Eureka Server(s):8761/eureka/apps/APP名称/实例ID</code>，请求返回HTTP状态码为200即为成功</li>
</ul></li>
</ul>

<h3 id="toc_6">instancer.go</h3>

<pre><code class="language-go">type Instancer struct {
    cache  *instance.Cache
    conn   fargoConnection
    app    string
    logger log.Logger
    quitc  chan chan struct{}
}

</code></pre>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[go-kit 服务发现(3) etcd]]></title>
    <link href="tongzhao.red/15612106758500.html"/>
    <updated>2019-06-22T21:37:55+08:00</updated>
    <id>tongzhao.red/15612106758500.html</id>
    <content type="html"><![CDATA[
<h2 id="toc_0">基本使用</h2>

<h3 id="toc_1">注册</h3>

<pre><code class="language-go">//Etcd客户端
client, _ := etcdv3.NewClient(context.Background(),[]string{&quot;http://localhost:2379&quot;},etcdv3.ClientOptions{})

//注册器
register := etcdv3.NewRegistrar(client,etcdv3.Service{
    Key: &quot;/services/hello/&quot;,
    Value: &quot;http://127.0.0.1:8080&quot;,
},logger)
register.Register()
defer register.Deregister()
</code></pre>

<h3 id="toc_2">发现</h3>

<pre><code class="language-go">//Etcd客户端
client,_ := etcdv3.NewClient(context.Background(),[]string{&quot;http://localhost:2379&quot;},etcdv3.ClientOptions{})

//服务实例
instancer , _ := etcdv3.NewInstancer(client,&quot;/services/hello/&quot;,logger)
</code></pre>

<h2 id="toc_3">底层原理</h2>

<h3 id="toc_4">目录结构</h3>

<pre><code>.
├── client.go 客户端
├── doc.go
├── example_test.go
├── instancer.go 服务实例
├── instancer_test.go
├── integration_test.go
├── registrar.go 注册器
└── registrar_test.go
</code></pre>

<p>目录中主要的是这三个文件，<strong>client.go</strong> <strong>instancer.go</strong> <strong>registrar.go</strong></p>

<h3 id="toc_5">client.go</h3>

<pre><code class="language-go">type Client interface {
    //获取一组value通过key前缀
    GetEntries(prefix string) ([]string, error)
    //watch指定前缀的key
    WatchPrefix(prefix string, ch chan struct{})
    //注册服务
    Register(s Service) error
    //注销服务
    Deregister(s Service) error
    //etcd 
    LeaseID() int64
}

type client struct {
    //etcd客户端使用v3版本api
    cli *clientv3.Client
    ctx context.Context
    //etcd key/value 操作实例
    kv clientv3.KV
    // etcd watcher 操作实例
    watcher clientv3.Watcher
    // watcher context
    wctx context.Context
    // watcher cancel func
    wcf context.CancelFunc
    // leaseID will be 0 (clientv3.NoLease) if a lease was not created
    leaseID clientv3.LeaseID

    //etcdKeepAlive实现心跳检测
    hbch &lt;-chan *clientv3.LeaseKeepAliveResponse
    // etcd Lease 操作实例
    leaser clientv3.Lease
}
func NewClient(ctx context.Context, machines []string, options ClientOptions) (Client, error)
</code></pre>

<p>主要包含以下6个函数</p>

<ul>
<li><strong>NewClient</strong>  创建etcd客户端，赋值给 client.cli</li>
<li><strong>GetEntries</strong> 通过 client.kv 获取value</li>
<li><strong>WatchPrefix</strong> 通过 client.watcher 监听key</li>
<li><strong>Deregister</strong> 通过 client.cli 服务绑定的key</li>
<li><strong>LeaseID</strong> return client.leaseID</li>
<li><strong>Register</strong>

<ul>
<li>初始化 client.leaser</li>
<li>初始化 client.watcher</li>
<li>初始化 client.kv</li>
<li>通过 client.kv 操作写入etcd，服务注册的key和value</li>
<li>创建 client.leaseID，默认心跳3秒，lease TTL9秒</li>
<li>client.leaser调用KeepAlive</li>
</ul></li>
</ul>

<h3 id="toc_6">registrar.go</h3>

<pre><code class="language-go">type Registrar struct {
    //etcd客户端
    client  Client
    //注册的服务
    service Service
    logger  log.Logger

    //服务Deregister并发锁
    quitmtx sync.Mutex
    //服务退出通道
    quit    chan struct{}
}

//服务的key和地址
type Service struct {
    Key   string // unique key, e.g. &quot;/service/foobar/1.2.3.4:8080&quot;
    Value string // returned to subscribers, e.g. &quot;http://1.2.3.4:8080&quot;
    TTL   *TTLOption
}

//服务心跳检测
type TTLOption struct {
    heartbeat time.Duration // e.g. time.Second * 3
    ttl       time.Duration // e.g. time.Second * 10
}

func NewTTLOption(heartbeat, ttl time.Duration) *TTLOption 
func NewRegistrar(client Client, service Service, logger log.Logger) *Registrar
func (r *Registrar) Register()
func (r *Registrar) Deregister()
</code></pre>

<p>主要包含以下4个函数</p>

<ul>
<li>NewTTLOption 心跳检测参数</li>
<li>NewRegistrar 创建 Registrar</li>
<li>Register 调用 <strong>client.go</strong>中 Register 方法</li>
<li>Deregister 调用 <strong>client.go</strong>中 Deregister 方法</li>
</ul>

<h3 id="toc_7">instancer.go</h3>

<pre><code class="language-go">type Instancer struct {
  //实例缓存
    cache  *instance.Cache
    //etcd客户端
    client Client
    //实例前缀
    prefix string
    logger log.Logger
    //Instancer 主动退出 通道
    quitc  chan struct{}
}

func NewInstancer(c Client, prefix string, logger log.Logger) (*Instancer, error) 
func (s *Instancer) loop() 
func (s *Instancer) Stop()
func (s *Instancer) Register(ch chan&lt;- sd.Event) 
func (s *Instancer) Deregister(ch chan&lt;- sd.Event)
</code></pre>

<p>主要包含以下5个函数</p>

<ul>
<li>NewInstancer 

<ul>
<li>调用 <strong>client.go</strong>  GetEntries函数，获取对应的一组服务地址</li>
<li>查询到的服务地址写入缓存 Instancer.cache</li>
</ul></li>
<li>loop 

<ul>
<li>监听服务对应的key</li>
</ul></li>
<li>Stop 

<ul>
<li>关闭服务监听</li>
</ul></li>
<li>Register </li>
<li>Deregister</li>
</ul>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[go-kit 服务发现(2) zookeeper]]></title>
    <link href="tongzhao.red/15612106649269.html"/>
    <updated>2019-06-22T21:37:44+08:00</updated>
    <id>tongzhao.red/15612106649269.html</id>
    <content type="html"><![CDATA[
<h2 id="toc_0">基本使用</h2>

<h3 id="toc_1">注册</h3>

<pre><code class="language-go">client, err := zk.NewClient([]string{&quot;localhost:2181&quot;},logger)
if err != nil{
    panic(err)
}

register := zk.NewRegistrar(client,zk.Service{
    Path: &quot;/services/hello&quot;,
    Name: &quot;abc&quot;,
    Data: []byte(&quot;http://127.0.0.1:8080&quot;),
},logger)

register.Register()
</code></pre>

<h3 id="toc_2">发现</h3>

<pre><code class="language-go">client, err := zk.NewClient([]string{&quot;localhost:2181&quot;},logger)
if err != nil{
    panic(err)
}

instancer , err := zk.NewInstancer(client,&quot;/services/hello/abc&quot;,logger)
if err != nil {
    panic(err)
}
duration := 500 * time.Millisecond
ctx := context.Background()
factory := helloFactory(ctx, &quot;GET&quot;, &quot;hello&quot;)
endpointer := sd.NewEndpointer(instancer, factory, logger)

endpointers,_ := endpointer.Endpoints()
</code></pre>

<h2 id="toc_3">底层原理</h2>

<h3 id="toc_4">目录结构</h3>

<pre><code>.
├── client.go 客户端
├── client_test.go
├── doc.go
├── instancer.go 服务实例
├── instancer_test.go
├── integration_test.go
├── logwrapper.go
├── registrar.go 注册器
└── util_test.go

</code></pre>

<p>目录中主要的是这三个文件，<strong>client.go</strong> <strong>instancer.go</strong> <strong>registrar.go</strong></p>

<h3 id="toc_5">client.go</h3>

<pre><code class="language-go">type Client interface {
    //获取一组value通过key前缀
    GetEntries(path string) ([]string, &lt;-chan zk.Event, error)
    //watch指定前缀的key
    CreateParentNodes(path string) error
    //注册服务
    Register(s *Service) error
    //注销服务
    Deregister(s *Service) error
    //停止zk链接
    Stop()
}


type client struct {
    *zk.Conn //组合 github.com/samuel/go-zookeeper/zk struct Conn
    clientConfig
    active bool
    quit   chan struct{}
}

func NewClient(servers []string, logger log.Logger, options ...Option) (Client, error)
func (c *client) CreateParentNodes(path string)
</code></pre>

<p>主要包含以下5个函数</p>

<ul>
<li><strong>NewClient</strong>  创建zk客户端，client.Conn</li>
<li><strong>GetEntries</strong> client.Get 获取value</li>
<li><strong>Deregister</strong> client.Delete 删除zk中指定的key</li>
<li><p><strong>CreateParentNodes</strong> 保证key中的父节点都已经被创建。由于zk创建子节点时父节点必须都存在，如:/a/b/c,当/a/b节点不存在时，/a/b/c节点无法创建</p></li>
<li><p><strong>Register</strong></p>

<ul>
<li>CreateParentNodes函数创建所有父节点，已存在则跳过</li>
<li>client.CreateProtectedEphemeralSequential函数，创建一个<strong>保护</strong> <strong>临时</strong> <strong>顺序</strong> 节点(ProtectedEphemeralSequential)，同时将value存在此节点中。</li>
<li>保护顺序临时节点

<ul>
<li>示例:_c_<strong>c83db041ac654566228b72cbd541bcb5</strong>-abc0000000006，其中加粗字体为GUID，_c_为默认前缀，abc0000000006为后缀</li>
<li><strong>临时</strong>节点，当zk链接会话关闭后，该节点就会被删除。</li>
<li><strong>顺序</strong>节点，创建的节点的名称以GUID作为前缀。如果节点创建失败，则会发生正常的重试机制。在重试过程中，首先搜索父路径，寻找包含GUID的节点。如果找到该节点，则假定它是第一次尝试成功创建并返回给调用者的丢失节点。</li>
<li><strong>保护</strong>节点,key自增后缀确保节点名称的唯一性</li>
</ul></li>
</ul></li>
</ul>

<h3 id="toc_6">registrar.go</h3>

<pre><code class="language-go">type Registrar struct {
  //zk客户端
    client  Client
    //注册的服务
    service Service
    logger  log.Logger
}

//服务的key和地址
type Service struct {
    Path string // 服务发现命名空间: /service/hello/
    Name string // 服务名称, example: abc
    Data []byte // 服务实例数据存在, 如: 10.0.2.10:80
    node string // 存储 ProtectedEphemeralSequential(保护临时顺序)节点的名称，便于Deregister函数注销服务
}

func NewRegistrar(client Client, service Service, logger log.Logger) *Registrar
func (r *Registrar) Register() 
func (r *Registrar) Deregister()
</code></pre>

<p>包含以下3个函数</p>

<ul>
<li>NewRegistrar 创建 Registrar</li>
<li>Register 调用 <strong>client.go</strong>中 Register 方法</li>
<li>Deregister 调用 <strong>client.go</strong>中 Deregister 方法</li>
</ul>

<h3 id="toc_7">instancer.go</h3>

<pre><code class="language-go">type Instancer struct {
  //实例缓存
    cache  *instance.Cache
    //zk客户端
    client Client
    //服务zk节点值
    path   string
    logger log.Logger
    //Instancer 主动退出 通道
    quitc  chan struct{}
}

func NewInstancer(c Client, path string, logger log.Logger) (*Instancer, error)
func (s *Instancer) loop(eventc &lt;-chan zk.Event)
func (s *Instancer) Stop()
func (s *Instancer) Register(ch chan&lt;- sd.Event) 
func (s *Instancer) state() sd.Event {
</code></pre>

<p>主要包含以下5个函数</p>

<ul>
<li>NewInstancer 

<ul>
<li>调用 <strong>client.go</strong>  GetEntries函数，获取对应的一组服务地址</li>
</ul></li>
<li>loop 

<ul>
<li>监听服务对应的key</li>
</ul></li>
<li>Stop 

<ul>
<li>关闭服务监听</li>
</ul></li>
<li>Register </li>
<li>Deregister</li>
</ul>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[go-kit 服务发现(1) consul]]></title>
    <link href="tongzhao.red/15612106518990.html"/>
    <updated>2019-06-22T21:37:31+08:00</updated>
    <id>tongzhao.red/15612106518990.html</id>
    <content type="html"><![CDATA[
<h2 id="toc_0">服务发现</h2>

<h3 id="toc_1">一、什么是服务？</h3>

<blockquote>
<p><a href="https://en.wikipedia.org/wiki/OASIS_(organization)">OASIS</a>将服务定义为“一种允许访问一个或多个功能的机制，其中使用指定的接口提供访问，并按照服务描述指定的约束和策略执行访问”。😰😰😰</p>
</blockquote>

<ul>
<li>业务模块（user/mission/vip）</li>
<li>基础组件（ipdb/uuid）</li>
<li>缓存服务（redis/memcached）</li>
<li>持久化服务（Mysql/ELS/MNS）</li>
<li>网络服务(nginx/lb)</li>
<li>...</li>
</ul>

<h3 id="toc_2">二、什么是服务发现？</h3>

<p>调用方无需知道服务提供者的网络位置(ip:端口等)，只需通过服务名称(如user/item/mission)，即可调用服务</p>

<h3 id="toc_3">三、为什么需要服务发现？</h3>

<blockquote>
<p>在现代的基于云计算的微服务应用中，服务实例会被动态地分配网络地址。并且，因为自动伸缩、故障和升级，服务实例会动态地改变。故而，你的客户端代码需要用一种更加精密的服务发现机制。而不是偶尔<strong>更新的配置文件中读取到网络地址</strong>。</p>
</blockquote>

<ol>
<li><p><strong>场景1：</strong> 需要新上线一个服务：</p>

<ul>
<li>提供者：配置域名、nginx、负载均衡、部署代码</li>
<li>调用方：配置服务域名，调用具体业务</li>
</ul></li>
<li><p><strong>场景2：</strong> 某个热点事件的出现，导致流量爆增，需要扩容：</p>

<ul>
<li>提供者：配置nginx、负载均衡、部署代码</li>
</ul></li>
<li><p><strong>服务发现场景1：</strong> 需要新上线一个服务：</p>

<ul>
<li>提供者：部署代码(包含注册服务)</li>
<li>调用方：部署代码(包含查询服务)</li>
</ul></li>
<li><p><strong>服务发现场景2：</strong> 某个热点事件的出现，导致流量爆增，需要扩容：</p>

<ul>
<li>提供者：部署代码（可配置自动扩容）</li>
</ul></li>
</ol>

<h3 id="toc_4">四、服务发现的流程</h3>

<pre><code class="language-sequence">Title: 客户端发现
提供者-&gt;注册中心: 注册服务
提供者--&gt;注册中心: 健康检查
消费者-&gt;注册中心: 查询服务提供者网络信息
注册中心-&gt;消费者: Ip:192.168.*.* Domain:8787
消费者-&gt;&gt;提供者: 访问服务
</code></pre>

<pre><code class="language-sequence">Title: 服务端发现
提供者-&gt;注册中心: 注册服务
提供者--&gt;注册中心: 健康检查
负载均衡器-&gt;注册中心: 查询服务提供者网络信息
消费者-&gt;负载均衡器: 查询服务提供者网络信息
负载均衡器-&gt;消费者: 转发 Ip:192.168.*.* Domain:8787
注册中心-&gt;负载均衡器: Ip:192.168.*.* Domain:8787
消费者-&gt;&gt;提供者: 访问服务
</code></pre>

<table>
<thead>
<tr>
<th></th>
<th>客户端</th>
<th>服务端</th>
</tr>
</thead>

<tbody>
<tr>
<td>请求数</td>
<td><strong>少一次</strong></td>
<td>多一次</td>
</tr>
<tr>
<td>消费者逻辑</td>
<td>内置服务发现逻辑</td>
<td><strong>无需客户端服务发现逻辑</strong></td>
</tr>
<tr>
<td>业界使用</td>
<td><strong>多一些</strong></td>
<td>少一些</td>
</tr>
</tbody>
</table>

<h3 id="toc_5">五、服务发现的现有解决方案</h3>

<p><a href="https://stackshare.io/stackups/consul-vs-eureka-vs-zookeeper">stackshare对比页面</a></p>

<table>
<thead>
<tr>
<th></th>
<th>ZooKeeper</th>
<th>Etcd</th>
<th><u>Eureka</u></th>
<th><u>Consul</u></th>
<th>DNSSrv</th>
</tr>
</thead>

<tbody>
<tr>
<td>多数据中心</td>
<td></td>
<td></td>
<td></td>
<td>✅</td>
<td></td>
</tr>
<tr>
<td>自带服务发现</td>
<td></td>
<td></td>
<td>✅</td>
<td>✅</td>
<td></td>
</tr>
<tr>
<td>自带健康检查</td>
<td></td>
<td></td>
<td>✅</td>
<td>✅</td>
<td></td>
</tr>
<tr>
<td>自带WebUi</td>
<td></td>
<td></td>
<td>✅</td>
<td>✅</td>
<td></td>
</tr>
<tr>
<td>分布式Key/Value存储</td>
<td>✅</td>
<td>✅</td>
<td></td>
<td>✅</td>
<td></td>
</tr>
<tr>
<td>开源</td>
<td>✅</td>
<td>✅</td>
<td>2.0闭源</td>
<td>✅</td>
<td></td>
</tr>
<tr>
<td>一致性</td>
<td>paxos</td>
<td>raft</td>
<td></td>
<td>raft</td>
<td></td>
</tr>
<tr>
<td>监控</td>
<td></td>
<td>metrics</td>
<td>metrics</td>
<td>metrics</td>
<td></td>
</tr>
<tr>
<td>使用接口(多语言能力)</td>
<td>客户端</td>
<td>http/grpc</td>
<td>http</td>
<td>http/dns</td>
<td></td>
</tr>
<tr>
<td>CAP</td>
<td>cp</td>
<td>cp</td>
<td>ap</td>
<td>cp</td>
<td></td>
</tr>
<tr>
<td>开发语言</td>
<td>JAVA</td>
<td>GO</td>
<td>JAVA</td>
<td>GO</td>
<td></td>
</tr>
</tbody>
</table>

<h2 id="toc_6">源码原理</h2>

<pre><code>├── endpoint_cache_test.go 
├── endpoint_cache.go
├── endpointer.go
├── endpointer_test.go
├── instancer.go
├── factory.go
├── benchmark_test.go
├── registrar.go
├── doc.go
├── etcd
│   ├── client_test.go
│   ├── client.go 
│   ├── integration_test.go 
│   ├── registrar.go 
│   ├── registrar_test.go 
│   ├── example_test.go
│   ├── instancer.go  
│   ├── instancer_test.go
│   └── doc.go
├── zk
│   ├── client.go
│   ├── integration_test.go
│   ├── client_test.go
│   ├── instancer_test.go
│   ├── util_test.go
│   ├── instancer.go
│   ├── registrar.go
│   ├── logwrapper.go
│   └── doc.go
├── consul
│   ├── instancer_test.go
│   ├── instancer.go
│   ├── client_test.go
│   ├── integration_test.go
│   ├── client.go 
│   ├── registrar.go 
│   ├── registrar_test.go
│   └── doc.go
├── etcdv3
│   ├── integration_test.go
│   ├── client.go
│   ├── registrar_test.go
│   ├── registrar.go
│   ├── example_test.go
│   ├── instancer.go
│   ├── instancer_test.go
│   └── doc.go
├── lb(负载均衡)
│   ├── retry_test.go
│   ├── retry.go （多次尝试请求Endpoint）
│   ├── round_robin_test.go
│   ├── random_test.go
│   ├── round_robin.go （轮询调度Endpoint）
│   ├── random.go （随机选择Endpoint）
│   ├── balancer.go （包含一个Endpoint的接口）
│   └── doc.go
├── eureka
│   ├── util_test.go
│   ├── registrar.go
│   ├── integration_test.go
│   ├── registrar_test.go
│   ├── instancer.go
│   ├── instancer_test.go
│   └── doc.go
├── dnssrv(通过net包的dns客户端，通过SRV记录实现服务发现 [DNS SRV介绍](https://www.lijiaocn.com/%E6%8A%80%E5%B7%A7/2017/03/06/dns-srv.html))
│   ├── instancer.go
│   ├── instancer_test.go
│   ├── lookup.go
│   └── doc.go
└── internal(内部通过管道实现的应用内服务发现)
    └── instance
</code></pre>

<h1 id="toc_7">consul</h1>

<h2 id="toc_8">基本使用</h2>

<h3 id="toc_9">注册</h3>

<pre><code class="language-go">var client consulsd.Client
{
    consulConfig := api.DefaultConfig()
    consulConfig.Address = &quot;localhost:8500&quot;
    consulClient, err := api.NewClient(consulConfig)
    if err != nil {
        logger.Log(&quot;err&quot;, err)
        os.Exit(1)
    }
    client = consulsd.NewClient(consulClient)
}

check := api.AgentServiceCheck{
    HTTP:     &quot;http://127.0.0.1:8080/health&quot;,
    Interval: &quot;10s&quot;,
    Timeout:  &quot;1s&quot;,
    Notes:    &quot;基础监控检查&quot;,
}

num := rand.Intn(100) // to make service ID unique
register := consulsd.NewRegistrar(client, &amp;api.AgentServiceRegistration{
    ID:      &quot;hello&quot; + strconv.Itoa(num),
    Name:    &quot;hello&quot;,
    Tags:    []string{&quot;hello&quot;, &quot;hi&quot;},
    Port:    8080,
    Address: &quot;http://127.0.0.1&quot;,
    Check:   &amp;check,
}, logger)

register.Register()
</code></pre>

<h3 id="toc_10">发现</h3>

<pre><code class="language-go">var client consulsd.Client
{
    consulConfig := api.DefaultConfig()

    consulConfig.Address = &quot;http://localhost:8500&quot;
    consulClient, err := api.NewClient(consulConfig)
    if err != nil {
        logger.Log(&quot;err&quot;, err)
        os.Exit(1)
    }
    client = consulsd.NewClient(consulClient)
}

tags := []string{}
passingOnly := true
duration := 500 * time.Millisecond
ctx := context.Background()
factory := helloFactory(ctx, &quot;GET&quot;, &quot;hello&quot;)
instancer := consulsd.NewInstancer(client, logger, &quot;hello&quot;, tags, passingOnly)
endpointer := sd.NewEndpointer(instancer, factory, logger)
balancer := lb.NewRoundRobin(endpointer)
retry := lb.Retry(1, duration, balancer)
res, _ := retry(ctx, struct{}{})
</code></pre>

<h2 id="toc_11">底层原理</h2>

<h3 id="toc_12">目录结构</h3>

<pre><code>.
├── client.go 
├── client_test.go
├── doc.go
├── instancer.go
├── instancer_test.go
├── integration_test.go
├── registrar.go
└── registrar_test.go

</code></pre>

<p>目录中主要的是这三个文件，<strong>client.go</strong> <strong>instancer.go</strong> <strong>registrar.go</strong></p>

<h3 id="toc_13">client.go</h3>

<pre><code class="language-go">// Client is a wrapper around the Consul API.
type Client interface {
    // Register a service with the local agent.
    Register(r *consul.AgentServiceRegistration) error

    // Deregister a service with the local agent.
    Deregister(r *consul.AgentServiceRegistration) error

    // Service
    Service(service, tag string, passingOnly bool, queryOpts *consul.QueryOptions) ([]*consul.ServiceEntry, *consul.QueryMeta, error)
}

type client struct {
    consul *consul.Client
}

// NewClient returns an implementation of the Client interface, wrapping a
// concrete Consul client.
func NewClient(c *consul.Client) Client {
    return &amp;client{consul: c}
}

func (c *client) Register(r *consul.AgentServiceRegistration) error {
    return c.consul.Agent().ServiceRegister(r)
}

func (c *client) Deregister(r *consul.AgentServiceRegistration) error {
    return c.consul.Agent().ServiceDeregister(r.ID)
}

func (c *client) Service(service, tag string, passingOnly bool, queryOpts *consul.QueryOptions) ([]*consul.ServiceEntry, *consul.QueryMeta, error) {
    return c.consul.Health().Service(service, tag, passingOnly, queryOpts)
}
</code></pre>

<p>主要包含以下4个函数</p>

<ul>
<li><strong>NewClient</strong>  创建consul客户端</li>
<li><strong>Register</strong> 注册服务</li>
<li><strong>Deregister</strong>  注销服务</li>
<li><strong>Service</strong> 获取服务/发现服务</li>
</ul>

<h3 id="toc_14">registrar.go</h3>

<pre><code class="language-go">// Registrar registers service instance liveness information to Consul.
type Registrar struct {
    client       Client
    registration *stdconsul.AgentServiceRegistration
    logger       log.Logger
}

// NewRegistrar returns a Consul Registrar acting on the provided catalog
// registration.
func NewRegistrar(client Client, r *stdconsul.AgentServiceRegistration, logger log.Logger) *Registrar {
    return &amp;Registrar{
        client:       client,
        registration: r,
        logger:       log.With(logger, &quot;service&quot;, r.Name, &quot;tags&quot;, fmt.Sprint(r.Tags), &quot;address&quot;, r.Address),
    }
}

// Register implements sd.Registrar interface.
func (p *Registrar) Register() {
    if err := p.client.Register(p.registration); err != nil {
        p.logger.Log(&quot;err&quot;, err)
    } else {
        p.logger.Log(&quot;action&quot;, &quot;register&quot;)
    }
}

// Deregister implements sd.Registrar interface.
func (p *Registrar) Deregister() {
    if err := p.client.Deregister(p.registration); err != nil {
        p.logger.Log(&quot;err&quot;, err)
    } else {
        p.logger.Log(&quot;action&quot;, &quot;deregister&quot;)
    }
}
</code></pre>

<p>包含以下3个函数</p>

<ul>
<li>NewRegistrar 创建 Registrar</li>
<li>Register 调用 <strong>client.go</strong>中 Register 方法</li>
<li>Deregister 调用 <strong>client.go</strong>中 Deregister 方法</li>
</ul>

<h3 id="toc_15">instancer.go</h3>

<pre><code class="language-go">// Instancer yields instances for a service in Consul.
type Instancer struct {
    cache       *instance.Cache
    client      Client
    logger      log.Logger
    service     string
    tags        []string
    passingOnly bool
    quitc       chan struct{}
}

// NewInstancer returns a Consul instancer that publishes instances for the
// requested service. It only returns instances for which all of the passed tags
// are present.
func NewInstancer(client Client, logger log.Logger, service string, tags []string, passingOnly bool) *Instancer 

// Stop terminates the instancer.
func (s *Instancer) Stop()

func (s *Instancer) loop(lastIndex uint64)

func (s *Instancer) getInstances(lastIndex uint64, interruptc chan struct{}) ([]string, uint64, error)

// Register implements Instancer.
func (s *Instancer) Register(ch chan&lt;- sd.Event)

// Deregister implements Instancer.
func (s *Instancer) Deregister(ch chan&lt;- sd.Event)

func makeInstances(entries []*consul.ServiceEntry) []string
</code></pre>

<p>主要包含以下5个函数</p>

<ul>
<li>NewInstancer 

<ul>
<li>调用 getInstances 函数，获取对应的一组服务地址，构造 Instancer 结构体</li>
</ul></li>
<li>loop 

<ul>
<li>循环查询调用 getInstances 函数，默认10毫秒调用一次</li>
</ul></li>
<li>Stop 

<ul>
<li>关闭服务监听， getInstances 函数获得一个 errStopped，停止loop</li>
</ul></li>
<li>Register </li>
<li>Deregister</li>
</ul>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[UUID那些事儿(中)]]></title>
    <link href="tongzhao.red/15128965238543.html"/>
    <updated>2017-12-10T17:02:03+08:00</updated>
    <id>tongzhao.red/15128965238543.html</id>
    <content type="html"><![CDATA[
<p>不遵守UUID协议规范的唯一识别码生成</p>

<h1 id="toc_0">twitter/snowflake</h1>

<p>【此节转载】</p>

<h2 id="toc_1">Snowflake算法核心</h2>

<p>把时间戳，工作机器id，序列号组合在一起。<br/>
<img src="http://static.tongzhao.red/img/20180123204222.jpg" alt="snowflake-64bit"/><br/>
除了最高位bit标记为不可用以外，其余三组bit占位均可浮动，看具体的业务需求而定。默认情况下41bit的时间戳可以支持该算法使用到2082年，10bit的工作机器id可以支持1023台机器，序列号支持1毫秒产生4095个自增序列id。下文会具体分析。</p>

<h2 id="toc_2">Snowflake – 时间戳</h2>

<p>这里时间戳的细度是毫秒级，具体代码如下，建议使用64位linux系统机器，因为有vdso，gettimeofday()在用户态就可以完成操作，减少了进入内核态的损耗。</p>

<pre><code class="language-c">uint64_t generateStamp()
{
    timeval tv;
    gettimeofday(&amp;tv, 0);
    return (uint64_t)tv.tv_sec * 1000 + (uint64_t)tv.tv_usec / 1000;
}
</code></pre>

<p>默认情况下有41个bit可以供使用，那么一共有T（1llu &lt;&lt; 41）毫秒供你使用分配，年份 = T / (3600 * 24 * 365 * 1000) = 69.7年。如果你只给时间戳分配39个bit使用，那么根据同样的算法最后年份 = 17.4年。</p>

<h2 id="toc_3">Snowflake – 工作机器id</h2>

<p>严格意义上来说这个bit段的使用可以是进程级，机器级的话你可以使用MAC地址来唯一标示工作机器，工作进程级可以使用IP+Path来区分工作进程。如果工作机器比较少，可以使用配置文件来设置这个id是一个不错的选择，如果机器过多配置文件的维护是一个灾难性的事情。</p>

<p>这里的解决方案是需要一个工作id分配的进程，可以使用自己编写一个简单进程来记录分配id，或者利用Mysql auto_increment机制也可以达到效果。<br/>
<img src="http://static.tongzhao.red/img/20180123204222.jpg" alt="snowflake-工作id"/></p>

<p>工作进程与工作id分配器只是在工作进程启动的时候交互一次，然后工作进程可以自行将分配的id数据落文件，下一次启动直接读取文件里的id使用。</p>

<p>PS：这个工作机器id的bit段也可以进一步拆分，比如用前5个bit标记进程id，后5个bit标记线程id之类</p>

<h2 id="toc_4">Snowflake – 序列号</h2>

<p>序列号就是一系列的自增id（多线程建议使用atomic），为了处理在同一毫秒内需要给多条消息分配id，若同一毫秒把序列号用完了，则“等待至下一毫秒”。</p>

<pre><code class="language-c">uint64_t waitNextMs(uint64_t lastStamp)
{
    uint64_t cur = 0;
    do {
        cur = generateStamp();
    } while (cur &lt;= lastStamp);
    return cur;
}
</code></pre>

<p>总体来说，是一个很高效很方便的GUID产生算法，一个int64_t字段就可以胜任，不像现在主流128bit的GUID算法，即使无法保证严格的id序列性，但是对于特定的业务，比如用做游戏服务器端的GUID产生会很方便。另外，在多线程的环境下，序列号使用atomic可以在代码实现上有效减少锁的密度。</p>

<h2 id="toc_5">语言实现</h2>

<ul>
<li>(官方实现-不维护)scala：<a href="https://github.com/twitter/snowflake/releases/tag/snowflake-2010">https://github.com/twitter/snowflake/releases/tag/snowflake-2010</a></li>
<li>不同语言github均有各自实现，下面主要介绍PHP实现</li>
</ul>

<h1 id="toc_6">PHP实现</h1>

<h2 id="toc_7">PHP的劣势</h2>

<p>snowflake算法需求在同一毫秒生成的code的序列号自增，由于PHP在语言级别上没有办法让某个对象常驻内存，所以需要借助的其他的方法实现。</p>

<h3 id="toc_8">扩展</h3>

<p><a href="https://github.com/Sxdd/php_snowflake">php_snowflake</a>，通过扩展实现了让PHP支持全局变量，原理和php的生命周期有关</p>

<h4 id="toc_9">PHP的生命周期</h4>

<p><img src="http://static.tongzhao.red/img/20180123204222.png" alt="02-01-01-cgi-lift-cycle"/></p>

<h4 id="toc_10">多进程SAPI生命周期</h4>

<p><img src="http://static.tongzhao.red/img/20180123204222.png" alt="02-01-02-multiprocess-life-cycle"/></p>

<p>通常PHP是编译为apache的一个模块来处理PHP请求。Apache一般会采用多进程模式， Apache启动后会fork出多个子进程，每个进程的内存空间独立，每个子进程都会经过开始和结束环节， 不过每个进程的开始阶段只在进程fork出来以来后进行，在整个进程的生命周期内可能会处理多个请求。 只有在Apache关闭或者进程被结束之后才会进行关闭阶段，在这两个阶段之间会随着每个请求重复请求开始-请求关闭的环节。 如图2.2所示：</p>

<h4 id="toc_11">多线程的SAPI生命周期</h4>

<p>多线程模式和多进程中的某个进程类似，不同的是在整个进程的生命周期<br/>
<img src="http://static.tongzhao.red/img/20180123204222.png" alt="02-01-013-multithreaded-lift-cycle"/></p>

<h4 id="toc_12">实现</h4>

<p>PHP扩展开发提供了注册module全局变量的功能<br/>
<a href="http://php.net/manual/zh/internals2.structure.globals.php">http://php.net/manual/zh/internals2.structure.globals.php</a></p>

<p>小细节：<br/>
由于进程关闭后序列会重置，所以算法使用的进程号+序列号保证唯一<br/>
提供了service_no可以分布式部署</p>

<h4 id="toc_13">此扩展的缺点</h4>

<ol>
<li>使用的的32位的字符串，占用空间更大</li>
<li>机器码使用的是进程号，分布式系统下有概率重复（使用不同的service_no则不会）</li>
<li>和snowflake的官方算法有差异，时间戳直接使用当前毫秒数，而非当前时间戳减去固定值（其实使用字符串形式这样更加易读，snowflake官方算法是在为了用更小的位数支持更长的时间）</li>
</ol>

<h1 id="toc_14">参考</h1>

<p><a href="http://www.lanindex.com/twitter-snowflake%EF%BC%8C64%E4%BD%8D%E8%87%AA%E5%A2%9Eid%E7%AE%97%E6%B3%95%E8%AF%A6%E8%A7%A3/">Twitter-Snowflake，64位自增ID算法详解</a><br/>
<a href="http://www.php-internals.com/book/?p=chapt02/02-01-php-life-cycle-and-zend-engine">PHP生命周期和Zend引擎</a></p>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[UUID那些事儿(下)]]></title>
    <link href="tongzhao.red/15128949132962.html"/>
    <updated>2017-12-10T16:35:13+08:00</updated>
    <id>tongzhao.red/15128949132962.html</id>
    <content type="html"><![CDATA[
<p>其他的方法</p>

<h2 id="toc_0">业务属性</h2>

<ul>
<li>滴滴：时间+起点编号+车牌号</li>
<li>淘宝订单：时间戳+用户ID</li>
<li>其他电商：时间戳+下单渠道+用户ID，有的会加上订单第一个商品的ID。</li>
</ul>

<p>举例roll业务场景--用户参与roll房间记录<br/>
时间+roll房间id+用户id</p>

<h2 id="toc_1">数据库方案-Flicker的解决方案</h2>

<table>
<thead>
<tr>
<th>自增id</th>
<th>调用方ip</th>
</tr>
</thead>

<tbody>
<tr>
<td>5</td>
<td>192.168.2.13</td>
</tr>
<tr>
<td>3</td>
<td>192.168.2.15</td>
</tr>
<tr>
<td>6</td>
<td>192.168.3.15</td>
</tr>
</tbody>
</table>

<h2 id="toc_2">Redis分布式方案</h2>

<h2 id="toc_3">推荐</h2>

<p><a href="https://tech.meituan.com/MT_Leaf.html">美团leaf</a><br/>
是现有大部分方案的优化方案</p>

<h2 id="toc_4">其他</h2>

<p><a href="http://shardingjdbc.io/docs/02-guide/key-generator/">shardingjdbc 本身就是中间件:-D</a></p>

<h2 id="toc_5">参考</h2>

<p><a href="https://mp.weixin.qq.com/s?__biz=MjM5MDI3MjA5MQ==&amp;mid=2697266651&amp;idx=2&amp;sn=77a5b0d4cabcbb00fafeb6a409b93cd7&amp;scene=21#wechat_redirect">分布式架构系统生成全局唯一序列号的一个思路</a><br/>
<a href="http://code.flickr.net/2010/02/08/ticket-servers-distributed-unique-primary-keys-on-the-cheap/">flickr方案原版(科学上网)</a><br/>
<a href="http://chuzhiyan.com/2016/08/22/%E7%A5%A8%E5%8A%A1%E6%9C%8D%E5%8A%A1%EF%BC%9A%E5%BB%89%E4%BB%B7%E7%9A%84%E5%88%86%E5%B8%83%E5%BC%8F%E5%94%AF%E4%B8%80%E4%B8%BB%E9%94%AE/">flickr方案网络版</a></p>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[UUID那些事儿(上)]]></title>
    <link href="tongzhao.red/15128797293311.html"/>
    <updated>2017-12-10T12:22:09+08:00</updated>
    <id>tongzhao.red/15128797293311.html</id>
    <content type="html"><![CDATA[
<pre><code>注意
UUID生成算法不保证返回值绝对唯一
170亿分之1的重复概率(数据来源维基百科)
</code></pre>

<h2 id="toc_0">UUID</h2>

<p>UUID 是 通用唯一识别码（Universally Unique Identifier），UUID的目的，是让分散式系统中的所有元素，都能有唯一的辨识资讯，而不需要透过中央控制端来做辨识资讯的指定。<br/>
目前最广泛应用的UUID，是微软公司的全局唯一标识符（GUID）</p>

<h2 id="toc_1">GUID</h2>

<p>全局唯一标识符，简称GUID（Globally Unique Identifier），通常表示成32个16进制数字（0－9，A－F）组成的字符串，如：{21EC2020-3AEA-1069-A2DD-08002B30309D}，它实质上是一个128位长的二进制整数。</p>

<h2 id="toc_2">UUID的格式</h2>

<p>xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx<br/>
   8位    4位   4位  4位     12位<br/>
M那个位置，代表版本号，由于UUID的标准实现有5个版本，所以只会是1,2,3,4,5<br/>
N那个位置，代表变种(Variant)，一般只会是8,9,a,b <br/>
<code>rfc4122协议10XX四位只能表示成8,9,A(16进制的10),B(16进制的11)</code></p>

<p>VariantNCS(已经过时的老古董,NCS=Apollo Network Computing System) 0xxx<br/>
VariantRFC4122(本文档) 10XX<br/>
VariantMicrosoft(微软) 110x<br/>
VariantFuture(未来) 111X</p>

<h2 id="toc_3">各个版本简介</h2>

<pre><code>http://www.uuid.online/ 可以演示不同版本的UUID
</code></pre>

<p>UUID目前协议规定的版本有5个，其中1、3、4版本较为常用</p>

<h3 id="toc_4">版本1</h3>

<pre><code>基于时间戳+MAC地址
</code></pre>

<p>通过当前时间戳、机器MAC地址生成；<br/>
由于在算法中使用了MAC地址，这个版本的UUID可以保证在全球范围的唯一性。<br/>
但与此同时，因为它暴露了电脑的MAC地址和生成这个UUID的时间，这就是这个版本UUID被诟病的地方。</p>

<h3 id="toc_5">版本2(大部分不使用)</h3>

<pre><code>基于时间戳+MAC地址
</code></pre>

<p>DCE安全的UUID和基于时间的UUID算法相同，但会把时间戳的前4位置换为POSIX的UID或GID。<br/>
不过，在UUID的规范里面没有明确地指定，所以基本上所有的UUID实现都不会实现这个版本。</p>

<h3 id="toc_6">版本3</h3>

<pre><code>基于命名空间的UUID(使用MD5散列化命名空间)
</code></pre>

<p>由用户指定1个namespace和1个具体的字符串，通过MD5散列，来生成1个UUID；<br/>
其中namespace根据协议规定，一般<br/>
domain name system, URLs, ISO Object IDs (OIDs), X.500 Distinguished<br/>
Names (DNs), and reserved words in a programming language. </p>

<h3 id="toc_7">版本4</h3>

<pre><code>基于随机数的UUID
</code></pre>

<p>最简单常用的一种</p>

<h3 id="toc_8">版本5</h3>

<pre><code>基于命名空间的UUID(使用SHA1散列化命名空间)
</code></pre>

<h2 id="toc_9">代码示例</h2>

<pre><code class="language-Python">import uuid
print &quot;v1=&quot;,uuid.uuid1()
print &quot;v3=&quot;,uuid.uuid3(uuid.NAMESPACE_DNS, &quot;myString&quot;)
print &quot;v4=&quot;,uuid.uuid4()
print &quot;v5=&quot;,uuid.uuid5(uuid.NAMESPACE_DNS, &quot;myString&quot;)
</code></pre>

<pre><code class="language-java">//java只实现了v3和v4
import java.util.UUID;

public class code
{
    public static void main(String[] args)
    {
        System.out.println(&quot;v3=&quot;+UUID.nameUUIDFromBytes(&quot;myString&quot;.getBytes()).toString());
        System.out.println(&quot;v4=&quot;+UUID.randomUUID());
    }
}
</code></pre>

<h2 id="toc_10">UUID和各个编程语言</h2>

<pre><code>注:PHP内置函数uniqid并不是uuid协议的实现
</code></pre>

<ul>
<li>PHP：<a href="https://github.com/ramsey/uuid">https://github.com/ramsey/uuid</a></li>
<li>Java：<a href="http://docs.oracle.com/javase/7/docs/api/java/util/UUID.html">http://docs.oracle.com/javase/7/docs/api/java/util/UUID.html</a></li>
<li>Golang：<a href="https://godoc.org/github.com/satori/go.uuid">https://godoc.org/github.com/satori/go.uuid</a></li>
<li>Android：<a href="http://developer.android.com/reference/java/util/UUID.html">http://developer.android.com/reference/java/util/UUID.html</a></li>
<li>IOS：<a href="https://developer.apple.com/documentation/foundation/uuid">https://developer.apple.com/documentation/foundation/uuid</a></li>
<li>nodejs - <a href="https://www.npmjs.com/package/uuid">https://www.npmjs.com/package/uuid</a></li>
<li>微软：<a href="http://msdn.microsoft.com/en-us/library/system.guid(v=vs.110).aspx">http://msdn.microsoft.com/en-us/library/system.guid(v=vs.110).aspx</a></li>
<li>Linux：<a href="http://en.wikipedia.org/wiki/Util-linux">http://en.wikipedia.org/wiki/Util-linux</a></li>
<li>MySQL:<a href="http://dev.mysql.com/doc/refman/5.1/en/miscellaneous-functions.html#function_uuid">http://dev.mysql.com/doc/refman/5.1/en/miscellaneous-functions.html#function_uuid</a></li>
</ul>

<h2 id="toc_11">PHP uniqid</h2>

<pre><code>//prefix 自定义字符串，如服务器的mac地址，ip等，保证多台机器同一微秒生成的值不重复
//more_entropy 会在返回的字符串结尾增加额外的熵,增加随机性
string uniqid ([ string $prefix = &quot;&quot; [, bool $more_entropy = false ]] )
</code></pre>

<p>获取一个带前缀、基于当前时间微秒数的唯一ID。</p>

<pre><code class="language-php">//代码示例
echo uniqid();//5a2cf3024ee36
</code></pre>

<pre><code class="language-c">//部分PHP源码
//uniqid.c
gettimeofday((struct timeval *) &amp;tv, (struct timezone *) NULL);
sec = (int) tv.tv_sec;
usec = (int) (tv.tv_usec % 0x100000);
    
if (more_entropy) {
    spprintf(&amp;uniqid, 0, &quot;%s%08x%05x%.8F&quot;, prefix, sec, usec, php_combined_lcg(TSRMLS_C) * 10);
} else {
    spprintf(&amp;uniqid, 0, &quot;%s%08x%05x&quot;, prefix, sec, usec);
}
</code></pre>

<h2 id="toc_12">参考</h2>

<p><a href="http://www.jianshu.com/p/d77f3ef0868a">关于UUID的二三事</a><br/>
<a href="https://tools.ietf.org/html/rfc4122">rfc4122</a><br/>
<a href="https://en.wikipedia.org/wiki/Universally_unique_identifier#Variants_and_versions">Universally unique identifier</a><br/>
<a href="https://zh.wikipedia.org/wiki/%E9%80%9A%E7%94%A8%E5%94%AF%E4%B8%80%E8%AF%86%E5%88%AB%E7%A0%81">通用唯一识别码</a></p>

]]></content>
  </entry>
  
</feed>
