Submitted by admin on 2016, December 12, 3:26 PM
简介
golang 中的创建一个新的 goroutine , 并不会返回像c语言类似的pid,所有我们不能从外部杀死某个goroutine,所有我就得让它自己结束,之前我们用 channel + select 的方式,来解决这个问题,但是有些场景实现起来比较麻烦,例如由一个请求衍生出的各个 goroutine 之间需要满足一定的约束关系,以实现一些诸如有效期,中止routine树,传递请求全局变量之类的功能。于是google 就为我们提供一个解决方案,开源了 context 包。使用 context 实现上下文功能约定需要在你的方法的传入参数的第一个传入一个 context.Context 类型的变量。
源码剖析
context.Context 接口
context 包的核心
// context 包里的方法是线程安全的,可以被多个 goroutine 使用
type Context interface {
// 当Context 被 canceled 或是 times out 的时候,Done 返回一个被 closed 的channel
Done() <-chan struct{}
// 在 Done 的 channel被closed 后, Err 代表被关闭的原因
Err() error
// 如果存在,Deadline 返回Context将要关闭的时间
Deadline() (deadline time.Time, ok bool)
// 如果存在,Value 返回与 key 相关了的值,不存在返回 nil
Value(key interface{}) interface{}
}
我们不需要手动实现这个接口,context 包已经给我们提供了两个,一个是 Background(),一个是 TODO(),这两个函数都会返回一个 Context 的实例。只是返回的这两个实例都是空 Context。
主要结构
cancelCtx 结构体继承了 Context ,实现了 canceler 方法:
//*cancelCtx 和 *timerCtx 都实现了canceler接口,实现该接口的类型都可以被直接canceled
type canceler interface {
cancel(removeFromParent bool, err error)
Done() <-chan struct{}
}
type cancelCtx struct {
Context
done chan struct{} // closed by the first cancel call.
mu sync.Mutex
children map[canceler]bool // set to nil by the first cancel call
err error // 当其被cancel时将会把err设置为非nil
}
func (c *cancelCtx) Done() <-chan struct{} {
return c.done
}
func (c *cancelCtx) Err() error {
c.mu.Lock()
defer c.mu.Unlock()
return c.err
}
func (c *cancelCtx) String() string {
return fmt.Sprintf("%v.WithCancel", c.Context)
}
//核心是关闭c.done
//同时会设置c.err = err, c.children = nil
//依次遍历c.children,每个child分别cancel
//如果设置了removeFromParent,则将c从其parent的children中删除
func (c *cancelCtx) cancel(removeFromParent bool, err error) {
if err == nil {
panic("context: internal error: missing cancel error")
}
c.mu.Lock()
if c.err != nil {
c.mu.Unlock()
return // already canceled
}
c.err = err
close(c.done)
for child := range c.children {
// NOTE: acquiring the child's lock while holding parent's lock.
child.cancel(false, err)
}
c.children = nil
c.mu.Unlock()
if removeFromParent {
removeChild(c.Context, c) // 从此处可以看到 cancelCtx的Context项是一个类似于parent的概念
}
}
timerCtx 结构继承 cancelCtx
type timerCtx struct {
cancelCtx //此处的封装为了继承来自于cancelCtx的方法,cancelCtx.Context才是父亲节点的指针
timer *time.Timer // Under cancelCtx.mu. 是一个计时器
deadline time.Time
}
valueCtx 结构继承 cancelCtx
type valueCtx struct {
Context
key, val interface{}
}
主要方法
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc)
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
func WithValue(parent Context, key interface{}, val interface{}) Context
WithCancel 对应的是 cancelCtx ,其中,返回一个 cancelCtx ,同时返回一个 CancelFunc,CancelFunc 是 context 包中定义的一个函数类型:type CancelFunc func()。调用这个 CancelFunc 时,关闭对应的c.done,也就是让他的后代goroutine退出。
WithDeadline 和 WithTimeout 对应的是 timerCtx ,WithDeadline 和 WithTimeout 是相似的,WithDeadline 是设置具体的 deadline 时间,到达 deadline 的时候,后代 goroutine 退出,而 WithTimeout 简单粗暴,直接 return WithDeadline(parent, time.Now().Add(timeout))。
WithValue 对应 valueCtx ,WithValue 是在 Context 中设置一个 map,拿到这个 Context 以及它的后代的 goroutine 都可以拿到 map 里的值。
详细 context 包源码解读:go源码解读
使用原则
使用 Context 的程序包需要遵循如下的原则来满足接口的一致性以及便于静态分析
不要把 Context 存在一个结构体当中,显式地传入函数。Context 变量需要作为第一个参数使用,一般命名为ctx
即使方法允许,也不要传入一个 nil 的 Context ,如果你不确定你要用什么 Context 的时候传一个 context.TODO
使用 context 的 Value 相关方法只应该用于在程序和接口中传递的和请求相关的元数据,不要用它来传递一些可选的参数
同样的 Context 可以用来传递到不同的 goroutine 中,Context 在多个goroutine 中是安全的
使用示例
例子copy自: 关于 Golang 中的 context 包的介绍
package main
import (
"fmt"
"time"
"golang.org/x/net/context"
)
// 模拟一个最小执行时间的阻塞函数
func inc(a int) int {
res := a + 1 // 虽然我只做了一次简单的 +1 的运算,
time.Sleep(1 * time.Second) // 但是由于我的机器指令集中没有这条指令,
// 所以在我执行了 1000000000 条机器指令, 续了 1s 之后, 我才终于得到结果。B)
return res
}
// 向外部提供的阻塞接口
// 计算 a + b, 注意 a, b 均不能为负
// 如果计算被中断, 则返回 -1
func Add(ctx context.Context, a, b int) int {
res := 0
for i := 0; i < a; i++ {
res = inc(res)
select {
case <-ctx.Done():
return -1
default:
}
}
for i := 0; i < b; i++ {
res = inc(res)
select {
case <-ctx.Done():
return -1
default:
}
}
return res
}
func main() {
{
// 使用开放的 API 计算 a+b
a := 1
b := 2
timeout := 2 * time.Second
ctx, _ := context.WithTimeout(context.Background(), timeout)
res := Add(ctx, 1, 2)
fmt.Printf("Compute: %d+%d, result: %d\n", a, b, res)
}
{
// 手动取消
a := 1
b := 2
ctx, cancel := context.WithCancel(context.Background())
go func() {
time.Sleep(2 * time.Second)
cancel() // 在调用处主动取消
}()
res := Add(ctx, 1, 2)
fmt.Printf("Compute: %d+%d, result: %d\n", a, b, res)
}
}
golang | 评论:0
| Trackbacks:0
| 阅读:548
Submitted by admin on 2016, December 8, 10:43 PM
杀死所有正在运行的容器
docker kill $(docker ps -a -q)
删除所有已经停止的容器
docker rm $(docker ps -a -q)
删除所有未打 dangling 标签的镜像
docker rmi $(docker images -q -f dangling=true)
删除所有镜像
docker rmi $(docker images -q)
为这些命令创建别名
# ~/.bash_aliases
# 杀死所有正在运行的容器.
alias dockerkill='docker kill $(docker ps -a -q)'
# 删除所有已经停止的容器.
alias dockercleanc='docker rm $(docker ps -a -q)'
# 删除所有未打标签的镜像.
alias dockercleani='docker rmi $(docker images -q -f dangling=true)'
# 删除所有已经停止的容器和未打标签的镜像.
alias dockerclean='dockercleanc || true && dockercleani'
另附上docker常用命令
docker version #查看版本
docker search tutorial#搜索可用docker镜像
docker pull learn/tutorial #下载镜像
docker run learn/tutorial echo "hello word"#在docker容器中运行hello world!
docker run learn/tutorial apt-get install -y ping#在容器中安装新的程序
保存镜像
首先使用docker ps -l命令获得安装完ping命令之后容器的id。然后把这个镜像保存为learn/ping。
提示:
1.运行docker commit,可以查看该命令的参数列表。
2.你需要指定要提交保存容器的ID。(译者按:通过docker ps -l 命令获得)
3.无需拷贝完整的id,通常来讲最开始的三至四个字母即可区分。(译者按:非常类似git里面的版本号)
正确的命令:
docker commit 698 learn/ping
运行新的镜像
docker run lean/ping ping www.google.com
检查运行中的镜像
现在你已经运行了一个docker容器,让我们来看下正在运行的容器。
使用docker ps命令可以查看所有正在运行中的容器列表,使用docker inspect命令我们可以查看更详细的关于某一个容器的信息。
目标:
查找某一个运行中容器的id,然后使用docker inspect命令查看容器的信息。
提示:
可以使用镜像id的前面部分,不需要完整的id。
正确的命令:
docker inspect efe
docker | 评论:0
| Trackbacks:0
| 阅读:642
Submitted by admin on 2016, December 8, 10:37 PM
删除所有未运行 Docker 容器
docker rm $(docker ps -a -q)
删除所有 Docker 镜像
删除所有未打 tag 的镜像
docker rmi $(docker images -q | awk '/^<none>/ { print $3 }')
删除所有镜像
docker rmi $(docker images -q)
根据格式删除所有镜像
docker rm $(docker ps -qf status=exited)
docker rmi $(docker images --filter dangling=true -q
执行这条命令删除pull过程或者create过程产生的临时镜像,剩下的都是有用的镜像。镜像是一层层叠加起来的,你使用的是带名字和tag的发布镜像,那些中间镜像没有名字,只有哈希,所以叫none。。
这个是列出所有的镜像的。因为某些镜像间存在依赖关系。你要知道docker跟git一样,有“层”的概念。你用docker pull ubuntu的时候就会发现,会pull下来一堆镜像,然后最后一个镜像tag为ubuntu:latest。
docker | 评论:0
| Trackbacks:0
| 阅读:715
Submitted by admin on 2016, December 8, 3:04 PM
Dockerfile文件的说明:
格式:
INSTRUCTION arguments
虽然指令忽略大小写,但建议用大写
# 是注释
MAINTAINER命令:
MAINTAINER命令用来指定维护者的姓名和联系方式
FROM命令:
FROM <image>/<image>:<tag>
这个是设置基本的镜像,为后续的命令使用,所以应该作为Dockerfile的第一条指令
比如:
FROM centos6-base
RUN 命令:
会上面FROM 指定的镜像里执行任何命令,然后提交(commit)结果,提交的镜像会在后面继续用到
格式:
RUN <command> (这个命令运行一个shell中 - '/bin/sh -c')
或:
RUN ["executable", "param1", "param2"]
RUN 命令等价于:
docker run image_name command
CMD and ENTRYPOINT
命令设置在容器启动时执行命令
ENTRYPOINT,表示镜像在初始化时需要执行的命令,不可被重写覆盖,需谨记
CMD,表示镜像运行默认参数,可被重写覆盖
ENTRYPOINT/CMD都只能在文件中存在一次,并且最后一个生效 多个存在,只有最后一个生效,其它无效!
需要初始化运行多个命令,彼此之间可以使用 && 隔开,但最后一个须要为无限运行的命令,需切记!
ENTRYPOINT/CMD,一般两者可以配合使用,比如:
ENTRYPOINT ["/usr/sbin/sshd"]
CMD ["-D"]
** 在Docker daemon模式下,无论你是使用ENTRYPOINT,还是CMD,最后的命令,一定要是当前进程需要一直运行的,才能够防容器退出。***
以下无效方式:
ENTRYPOINT service tomcat7 start #运行几秒钟之后,容器就会退出
CMD service tomcat7 start #运行几秒钟之后,容器就会退出
这样有效:
ENTRYPOINT service tomcat7 start && tail -f /var/lib/tomcat7/logs/catalina.out
# 或者
CMD service tomcat7 start && tail -f /var/lib/tomcat7/logs/catalina.out
这样也有效:
ENTRYPOINT ["/usr/sbin/sshd"]
CMD ["-D"]
USER 命令
比如指定 memcached 的运行用户,可以使用上面的 ENTRYPOINT or CMD来实现:
ENTRYPOINT ["memcached", "-u", "daemon"]
更好的方式:
ENTRYPOINT ["memcached"]
USER daemon
EXPOSE 命令
EXPOSE 命令可以设置一个端口在运行的镜像中暴露在外
在docker使用--link来链接两容器时会用到相关端口
EXPOSEd <port>
ENV命令:
用于设置环境变更
使用此dockerfile生成的image新建container,可以通过 docker inspect CONTAINER ID 看到这个环境变量
也可以通过在docker run时设置或修改环境变量
ADD 命令:
从src复制文件到container的dest路径:
ADD <src> <dest>
<src> 是相对被构建的源目录的相对路径,可以是文件或目录的路径,也可以是一个远程的文件url
<dest> 是container中的绝对路径
VOLUME 命令
VOLUME ["<mountpoint>"]
如:
VOLUME ["/data"]
创建一个挂载点用于共享目录
WORKDIR 命令
WORKDIR /path/to/workdir
配置RUN, CMD, ENTRYPOINT 命令设置当前工作路径
可以设置多次,如果是相对路径,则相对前一个 WORKDIR 命令
比如:
WORKDIR /a WORKDIR b WORKDIR c RUN pwd
其实是在 /a/b/c 下执行 pwd
Dockerfile文件到此介绍完毕。
下面是一个完整的Dockerfile文件
#Dockerfile
FROM centos6-base
MAINTAINER zhou_mfk <zhou_mfk@163.com>
RUN ssh-keygen -q -N "" -t dsa -f /etc/ssh/ssh_host_dsa_key
RUN ssh-keygen -q -N "" -t rsa -f /etc/ssh/ssh_host_rsa_key
RUN sed -ri 's/session required pam_loginuid.so/#session required pam_loginuid.so/g' /etc/pam.d/sshd
RUN mkdir -p /root/.ssh && chown root.root /root && chmod 700 /root/.ssh
EXPOSE 22
RUN echo 'root:redhat' | chpasswd
RUN yum install -y yum-priorities && rpm -ivh http://dl.fedoraproject.org/pub/epel/6/x86_64/epel-release-6-8.noarch.rpm && rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-6
RUN yum install tar gzip gcc vim wget -y
ENV LANG en_US.UTF-8
ENV LC_ALL en_US.UTF-8
CMD /usr/sbin/sshd -D
#End
docker | 评论:0
| Trackbacks:0
| 阅读:666
Submitted by admin on 2016, December 7, 11:33 PM
github | 评论:0
| Trackbacks:0
| 阅读:625
Submitted by admin on 2016, December 7, 11:31 PM
1、通过yum安装
需要root或者能sudo的权限
yum包更新到最新
$ sudo yum update
添加Docker yum源
$ sudo tee /etc/yum.repos.d/docker.repo <<-'EOF'
[dockerrepo]
name=Docker Repository
baseurl=https://yum.dockerproject.org/repo/main/centos/$releasever/
enabled=1
gpgcheck=1
gpgkey=https://yum.dockerproject.org/gpg
EOF
安装Docker包
$ sudo yum install docker-engine
启动Docker守护进程
$ sudo service docker start
通过测试镜像运行一个容器来验证Docker是否安装正确
$ sudo docker run hello-world
2、通过脚本安装
需要root或者能sudo的权限
yum包更新到最新
$ sudo yum update
运行Docker安装脚本
$ curl -sSL https://get.docker.com/ | sh
这个脚本会添加docker.repo仓库并且安装Docker
启动Docker守护进程
$ sudo service docker start
通过测试镜像运行一个容器来验证Docker是否安装正确
$ sudo docker run hello-world
创建一个Docker用户组
docker守护进程绑定在Unix socket 而不是 TCP 端口。默认情况下Unix socket属于root用户和具有sudo权限用户的使用范畴。出于这个原因,docker守护进程需要一直在root用户下面运行。
To avoid having to use sudo when you use the docker command, create a Unix group called docker and add users to it. When the docker daemon starts, it makes the ownership of the Unix socket read/writable by the docker group.
为了避免运行docker命令的时候需要使用sudo命令,创建一个叫docker的Unix用户组并将需要运行docker的用户添加进去。当开始运行docker守护进程,会令docker用户组的成员拥有Unix socket的读写权限。
创建docker用户组并添加用户:
1.需要root或者能sudo的权限
2.创建docker用户组并添加用户
sudo usermod -aG docker your_username
3.退出并且登录到your_username,如果在your_username下也建议退出再重新登录一次,这确保您的用户正在运行正确的权限。
4.验证运行docker已不再需要sudo命令
$ docker run hello-world
开机运行docker
$ sudo chkconfig docker on
docker | 评论:0
| Trackbacks:0
| 阅读:567
Submitted by admin on 2016, December 7, 11:27 PM
之前的一段时间,突然这个后台的编辑器用不了了,发的文章也没有格式了,很不好看,慢慢,也就没有更新了
那时,也有找过原因,还以为是服务器的原因,但没找到也没解决
时间好快,又一年了
最近,开始学习,发现,没个博客记录些东西,还真麻烦
所以,再次偿试解决
在本地安装了个新的,也是一样的问题
所以,觉得,应该是服务器的问题了,而程序或是兼容性的问题
好吧,看代码
没多久,还真找到原因了
fckeditor_php5.php
这个文件里,有一个判断浏览器信息的,默认找不到匹配信息,返回假,所以没有编辑了
只需改为真,就OK了
又开始更新吧
一个小插曲
在刚看代码时,想百度搜索下,看有没人遇到这个问题或有没解决办法,毕竟觉得,用这个博客程序的人,还是有一些
但没找到,却无意间发现,这个程序的作者入狱了,还判了5年,挺震惊,也挺悲剧的
生活闲谈 | 评论:0
| Trackbacks:0
| 阅读:608
Submitted by admin on 2015, December 11, 10:22 AM
2,apache配置:
用mod_rpaf来获取IP的
所以需要安装这个模块
下载:http://stderr.net/apache/rpaf/download/
tar zxvf mod_rpaf-0.6.tar.gz
cd mod_rpaf-0.6
/usr/local/www/apache/bin/apxs -i -c -n mod_rpaf-2.0.so mod_rpaf-2.0.c
配置apache:
在 httpd.conf中添加
LoadModule rpaf_module libexec/apache2/mod_rpaf-2.0.so
RPAFenable On
RPAFsethostname On
RPAFproxy_ips 192.168.1.1 #这个是前段的IP,可不是后端的IP哦
RPAFheader X-Forwarded-For
nginx | 评论:0
| Trackbacks:0
| 阅读:751