python sqlalchemy执行原始sql语句示例

参考文档

官方文档首页: http://docs.sqlalchemy.org/en/latest/

连接数据库

这边使用sqlite的内存数据库,方便测试

import sqlalchemy

db_engine = sqlalchemy.create_engine('sqlite:///:memory:', echo=True)
db_conn = db_engine.connect()

使用create_engine可以建立一个连接池,调用connect可以从连接池中获取一个连接,调用db_conn.close()会把连接释放给连接池,并做相应的清理工作。

官方说明:http://docs.sqlalchemy.org/en/latest/core/connections.html#basic-usage

The connection is an instance of Connection, which is a proxy object for an actual DBAPI connection. The DBAPI connection is retrieved from the connection pool at the point at which Connection is created.
The returned result is an instance of ResultProxy, which references a DBAPI cursor and provides a largely compatible interface with that of the DBAPI cursor. The DBAPI cursor will be closed by the ResultProxy when all of its result rows (if any) are exhausted. A ResultProxy that returns no rows, such as that of an UPDATE statement (without any returned rows), releases cursor resources immediately upon construction.
When the close() method is called, the referenced DBAPI connection is released to the connection pool. From the perspective of the database itself, nothing is actually “closed”, assuming pooling is in use. The pooling mechanism issues a rollback() call on the DBAPI connection so that any transactional state or locks are removed, and the connection is ready for its next usage.

使用原始SQL

先进行建表操作

db_conn.execute(r'''
CREATE TABLE IF NOT EXISTS stocks (
        date text, 
        trans text, 
        symbol text, 
        qty real, 
        pricereal
    )
''')

插入单条数据

直接拼装一个完整的sql字符串,或使用下面防注入的参数绑定方式, 注意:会自动commit

db_conn.execute(r'''
INSERT INTO stocks VALUES (?, ?, ?, ?, ?)
''', ('2001-01-11', 'BUY', 'RHAT', 100, 35.14) )
db_conn.execute(r'''
INSERT INTO stocks VALUES (:1, :2, :3, :4, :5)
''', ('2001-01-11', 'BUY', 'RHAT', 100, 35.14) )

操作日志显示会自动commit

2017-08-27 15:05:26,834 INFO sqlalchemy.engine.base.Engine ()
2017-08-27 15:05:26,834 INFO sqlalchemy.engine.base.Engine COMMIT
2017-08-27 15:05:26,834 INFO sqlalchemy.engine.base.Engine 
INSERT INTO stocks VALUES (:1, :2, :3, :4, :5)

2017-08-27 15:05:26,834 INFO sqlalchemy.engine.base.Engine ('2001-01-11', 'BUY', 'RHAT', 100, 35.14)
2017-08-27 15:05:26,834 INFO sqlalchemy.engine.base.Engine COMMIT
2017-08-27 15:05:26,835 INFO sqlalchemy.engine.base.Engine 
INSERT INTO stocks VALUES (?, ?, ?, ?, ?)

2017-08-27 15:05:26,835 INFO sqlalchemy.engine.base.Engine ('2001-01-11', 'BUY', 'RHAT', 100, 35.14)
2017-08-27 15:05:26,835 INFO sqlalchemy.engine.base.Engine COMMIT
2017-08-27 15:05:26,835 INFO sqlalchemy.engine.base.Engine 
INSERT INTO stocks VALUES (:1, :2, :3, :4, :5)

插入多条数据

与插入单条数据类似, 注意:会自动commit

purchases = [('2006-03-28', 'BUY', 'IBM', 1000, 45.00),
             ('2006-04-05', 'BUY', 'MSFT', 1000, 72.00),
             ('2006-04-06', 'SELL', 'IBM', 500, 53.00),
            ]
db_conn.execute(r'''
INSERT INTO stocks VALUES (:1, :2, :3, :4, :5)
''', purchases)
db_conn.execute(r'''
INSERT INTO stocks VALUES (?, ?, ?, ?, ?)
''', purchases)

操作日志显示会自动commit

2017-08-27 15:05:26,835 INFO sqlalchemy.engine.base.Engine 
INSERT INTO stocks VALUES (:1, :2, :3, :4, :5)

2017-08-27 15:05:26,835 INFO sqlalchemy.engine.base.Engine [('2006-03-28', 'BUY', 'IBM', 1000, 45.0), ('2006-04-05', 'BUY', 'MSFT', 1000, 72.0), ('2006-04-06', 'SELL', 'IBM', 500, 53.0)]
2017-08-27 15:05:26,835 INFO sqlalchemy.engine.base.Engine COMMIT
2017-08-27 15:05:26,835 INFO sqlalchemy.engine.base.Engine 
INSERT INTO stocks VALUES (?, ?, ?, ?, ?)

2017-08-27 15:05:26,835 INFO sqlalchemy.engine.base.Engine [('2006-03-28', 'BUY', 'IBM', 1000, 45.0), ('2006-04-05', 'BUY', 'MSFT', 1000, 72.0), ('2006-04-06', 'SELL', 'IBM', 500, 53.0)]
2017-08-27 15:05:26,835 INFO sqlalchemy.engine.base.Engine COMMIT

使用事务处理

参考文档: http://docs.sqlalchemy.org/en/latest/core/connections.html#using-transactions

由于上面的插入操作会自动进行commit,sqlalchemy提供了Transactions来管理commit和rollback。

完整的测试代码如下

import sqlalchemy

db_engine = sqlalchemy.create_engine('sqlite:///:memory:', echo=True, pool_size=2)
db_conn = db_engine.connect()

db_conn.execute(r'''
CREATE TABLE IF NOT EXISTS stocks (date text, trans text, symbol text, qty real, price real)
''')

with db_conn.begin() as db_trans:
    #
    db_conn.execute(r'''
    INSERT INTO stocks VALUES (:1, :2, :3, :4, :5)
    ''', ('aa', 'BUY', 'RHAT', 100, 35.14) )

    db_conn.execute(r'''
    INSERT INTO stocks VALUES (?, ?, ?, ?, ?)
    ''', ('bb', 'BUY', 'RHAT', 100, 35.14) )

    #提前进行commit
    db_trans.commit()

    purchases = [('cc', 'BUY', 'IBM', 1000, 45.00),
                 ('dd', 'BUY', 'MSFT', 1000, 72.00),
                 ('ee', 'SELL', 'IBM', 500, 53.00),
                ]
    db_conn.execute(r'''
    INSERT INTO stocks VALUES (?,?,?,?,?)
    ''', purchases)


query_result = db_conn.execute(r'''
SELECT * FROM  stocks
''').fetchall()
print('query_result:', query_result)

完整的日志如下

2017-08-27 15:18:22,057 INFO sqlalchemy.engine.base.Engine SELECT CAST('test plain returns' AS VARCHAR(60)) AS anon_1
2017-08-27 15:18:22,057 INFO sqlalchemy.engine.base.Engine ()
2017-08-27 15:18:22,057 INFO sqlalchemy.engine.base.Engine SELECT CAST('test unicode returns' AS VARCHAR(60)) AS anon_1
2017-08-27 15:18:22,057 INFO sqlalchemy.engine.base.Engine ()
2017-08-27 15:18:22,058 INFO sqlalchemy.engine.base.Engine 
CREATE TABLE IF NOT EXISTS stocks (date text, trans text, symbol text, qty real, price real)

2017-08-27 15:18:22,058 INFO sqlalchemy.engine.base.Engine ()
2017-08-27 15:18:22,058 INFO sqlalchemy.engine.base.Engine COMMIT
2017-08-27 15:18:22,058 INFO sqlalchemy.engine.base.Engine BEGIN (implicit)
2017-08-27 15:18:22,058 INFO sqlalchemy.engine.base.Engine 
    INSERT INTO stocks VALUES (:1, :2, :3, :4, :5)

2017-08-27 15:18:22,058 INFO sqlalchemy.engine.base.Engine ('aa', 'BUY', 'RHAT', 100, 35.14)
2017-08-27 15:18:22,058 INFO sqlalchemy.engine.base.Engine 
    INSERT INTO stocks VALUES (?, ?, ?, ?, ?)

2017-08-27 15:18:22,059 INFO sqlalchemy.engine.base.Engine ('bb', 'BUY', 'RHAT', 100, 35.14)
2017-08-27 15:18:22,059 INFO sqlalchemy.engine.base.Engine COMMIT
2017-08-27 15:18:22,059 INFO sqlalchemy.engine.base.Engine 
    INSERT INTO stocks VALUES (?,?,?,?,?)

2017-08-27 15:18:22,059 INFO sqlalchemy.engine.base.Engine [('cc', 'BUY', 'IBM', 1000, 45.0), ('dd', 'BUY', 'MSFT', 1000, 72.0), ('ee', 'SELL', 'IBM', 500, 53.0)]
2017-08-27 15:18:22,059 INFO sqlalchemy.engine.base.Engine COMMIT
2017-08-27 15:18:22,059 INFO sqlalchemy.engine.base.Engine 
SELECT * FROM  stocks

2017-08-27 15:18:22,059 INFO sqlalchemy.engine.base.Engine ()
query_result: [('aa', 'BUY', 'RHAT', 100.0, 35.14), ('bb', 'BUY', 'RHAT', 100.0, 35.14), ('cc', 'BUY', 'IBM', 1000.0, 45.0), ('dd', 'BUY', 'MSFT', 1000.0, 72.0), ('ee', 'SELL', 'IBM', 500.0, 53.0)]

可以看到有三次commit操作,有三次commit操作, 一次是建表, 两次是插入,其中第二次是显示调用commit,第三次是with结束后自动调用。因此如果想避免频繁的commit,可以使用with来进行上下文管理。

测试异常

import sqlalchemy

db_engine = sqlalchemy.create_engine('sqlite:///:memory:', echo=True, pool_size=2)
db_conn = db_engine.connect()

try:
    with db_conn.begin() as db_trans:
        db_conn.execute(r'''
        CREATE TABLE IF NOT EXISTS stocks (date text, trans text, symbol text, qty real, price real)
        ''')

        db_conn.execute(r'''
        INSERT INTO stocks VALUES (:1, :2, :3, :4, :5)
        ''', ('aa', 'BUY', 'RHAT', 100, 35.14) )
        db_trans.commit()

        db_conn.execute(r'''
        INSERT INTO stocks VALUES (:1, :2, :3, :4, :5)
        ''', ('bb', 'BUY', 'RHAT', 100, 35.14) )

        db_conn.execute(r'''
        INSERT INTO stocks VALUES (:1, :2, :3, :4, :5)
        ''', ('cc', 'BUY', 'RHAT', 100) ) #这里故意少传一个参数来制造异常
except:
    print('exception!!!')


query_result = db_conn.execute(r'''
SELECT * FROM  stocks
''').fetchall()
print('query_result:', query_result)

日志

2017-08-27 15:40:53,151 INFO sqlalchemy.engine.base.Engine SELECT CAST('test plain returns' AS VARCHAR(60)) AS anon_1
2017-08-27 15:40:53,151 INFO sqlalchemy.engine.base.Engine ()
2017-08-27 15:40:53,152 INFO sqlalchemy.engine.base.Engine SELECT CAST('test unicode returns' AS VARCHAR(60)) AS anon_1
2017-08-27 15:40:53,152 INFO sqlalchemy.engine.base.Engine ()
2017-08-27 15:40:53,152 INFO sqlalchemy.engine.base.Engine BEGIN (implicit)
2017-08-27 15:40:53,152 INFO sqlalchemy.engine.base.Engine 
        CREATE TABLE IF NOT EXISTS stocks (date text, trans text, symbol text, qty real, price real)

2017-08-27 15:40:53,153 INFO sqlalchemy.engine.base.Engine ()
2017-08-27 15:40:53,153 INFO sqlalchemy.engine.base.Engine 
        INSERT INTO stocks VALUES (:1, :2, :3, :4, :5)

2017-08-27 15:40:53,153 INFO sqlalchemy.engine.base.Engine ('aa', 'BUY', 'RHAT', 100, 35.14)
2017-08-27 15:40:53,153 INFO sqlalchemy.engine.base.Engine COMMIT
2017-08-27 15:40:53,153 INFO sqlalchemy.engine.base.Engine 
        INSERT INTO stocks VALUES (:1, :2, :3, :4, :5)

2017-08-27 15:40:53,153 INFO sqlalchemy.engine.base.Engine ('bb', 'BUY', 'RHAT', 100, 35.14)
2017-08-27 15:40:53,153 INFO sqlalchemy.engine.base.Engine COMMIT
2017-08-27 15:40:53,153 INFO sqlalchemy.engine.base.Engine 
        INSERT INTO stocks VALUES (:1, :2, :3, :4, :5)

2017-08-27 15:40:53,153 INFO sqlalchemy.engine.base.Engine ('cc', 'BUY', 'RHAT', 100)
2017-08-27 15:40:53,154 INFO sqlalchemy.engine.base.Engine ROLLBACK
exception!!!
2017-08-27 15:40:53,154 INFO sqlalchemy.engine.base.Engine 
SELECT * FROM  stocks

2017-08-27 15:40:53,154 INFO sqlalchemy.engine.base.Engine ()
query_result: [('aa', 'BUY', 'RHAT', 100.0, 35.14), ('bb', 'BUY', 'RHAT', 100.0, 35.14)]
  • 出现异常时会自动rollback
  • 如果在with中提前调用commit,会导致以后的每个改动操作都会自动commit

tornado分页实现-从本质到完全实现

初始化tornado目录

未分类

构建项目–实现post提交,get展示数据

实现类似

未分类

代码逻辑

未分类

完整代码

start.py

#!/usr/bin/env python
# coding=utf-8
import time
import tornado.ioloop
import tornado.web


# 业务逻辑处理模块


# 配置选项模块
from controllers import home


# 静态文件和模板文件的配置
settings = {
    'template_path': 'templates',
    'static_path': 'statics',
}

# 路由模块
## 动态路由系统
application = tornado.web.Application([
    (r"/index/(?P<page>d+)/(?P<nid>d+)", home.IndexHandler),
],
    **settings
)

## wsgi模块
if __name__ == "__main__":
    application.listen(8888)
    tornado.ioloop.IOLoop.instance().start()
controllers/home.py

#!/usr/bin/env python
# coding=utf-8
import tornado.web

LIST_INFO = [
    {"username": "maotai", "email": "123456"},
]


class IndexHandler(tornado.web.RequestHandler):
    # def get(self, *args, **kwargs):
    def get(self,page,nid):
        # print(page,nid)
        self.render("home/index.html", list_info=LIST_INFO)

    def post(self, *args, **kwargs):
        username = self.get_argument("username", "")
        email = self.get_argument("email", "")

        tmp = {"username": username, "email": email}
        LIST_INFO.append(tmp)
        self.redirect("/index/1/1")
home/index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>index</title>
</head>
<body>
<h1>添加数据</h1>
<form action="/index/1/1" method="post">
    <input type="text" name="username">
    <input type="email" name="email">
    <input type="submit" typeof="提交">
</form>
<h1>显示数据</h1>
<table border="1">
    <thead>
        <tr>
            <th>用户名</th>
            <th>邮箱</th>
        </tr>
    </thead>
    <tbody>
        {% for line in list_info %}
            <tr>
                <td>{{ line['username'] }}</td>
                <td>{{ line['email'] }}</td>
            </tr>
        {% end %}
    </tbody>
</table>
</body>
</html>

实现url //

未分类

实现分页-每页显示5条数据

效果:

未分类

代码逻辑:

未分类

完整代码:

#!/usr/bin/env python
# coding=utf-8
import time
import tornado.ioloop
import tornado.web


# 业务逻辑处理模块


# 配置选项模块
from controllers import home


# 静态文件和模板文件的配置
settings = {
    'template_path': 'templates',
    'static_path': 'statics',
}

# 路由模块
## 动态路由系统
application = tornado.web.Application([
    (r"/index/(?P<page>d*)", home.IndexHandler),
],
    **settings
)

## wsgi模块
if __name__ == "__main__":
    application.listen(8888)
    tornado.ioloop.IOLoop.instance().start()
#!/usr/bin/env python
# coding=utf-8
import tornado.web

LIST_INFO = [
    {"username": "maotai", "email": "123456"},
]


class IndexHandler(tornado.web.RequestHandler):
    # def get(self, *args, **kwargs):
    def get(self, page):
        # 每页显示5条数据
        # LIST_INFO[0:5]
        # LIST_INFO[5:10]
        page = int(page)
        print(page)
        start = (page - 1) * 5
        end = page * 5
        current_list = LIST_INFO[start:end]
        self.render("home/index.html", list_info=current_list)

    def post(self, *args, **kwargs):
        username = self.get_argument("username", "")
        email = self.get_argument("email", "")

        tmp = {"username": username, "email": email}
        LIST_INFO.append(tmp)
        self.redirect("/index/1")
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>index</title>
</head>
<body>
<h1>添加数据</h1>
<form action="/index/1" method="post">
    <input type="text" name="username">
    <input type="email" name="email">
    <input type="submit" typeof="提交">
</form>
<h1>显示数据</h1>
<table border="1">
    <thead>
        <tr>
            <th>用户名</th>
            <th>邮箱</th>
        </tr>
    </thead>
    <tbody>
        {% for line in list_info %}
            <tr>
                <td>{{ line['username'] }}</td>
                <td>{{ line['email'] }}</td>
            </tr>
        {% end %}
    </tbody>
</table>
</body>
</html>

处理page异常

  • page为空 如page=
  • page转换int不成功, 如page=xfdsfd213
  • page<=0情况

思路:

使用try except来操作

class IndexHandler(tornado.web.RequestHandler):
    # def get(self, *args, **kwargs):
    def get(self, page):
        # 每页显示5条数据
        # LIST_INFO[0:5]
        # LIST_INFO[5:10]

        # c        
        # if not page:
        #     page = 1
        try:
            page = int(page)
        except:
            page = 1
        if page <= 0:
            page = 1

        print(page)
        start = (page - 1) * 5
        end = page * 5
        current_list = LIST_INFO[start:end]
        self.render("home/index.html", list_info=current_list)

    def post(self, *args, **kwargs):
        username = self.get_argument("username", "")
        email = self.get_argument("email", "")

        tmp = {"username": username, "email": email}
        LIST_INFO.append(tmp)
        self.redirect("/index/1")

实现提交数据后,不总是跳转到index/1,而是和get时候index/x一样

实现效果

未分类

代码逻辑

未分类

完整代码:

#!/usr/bin/env python
# coding=utf-8
import tornado.web

LIST_INFO = [
    {"username": "maotai", "email": "123456"},
]


class IndexHandler(tornado.web.RequestHandler):
    # def get(self, *args, **kwargs):
    def get(self, page):
        try:
            page = int(page)
        except:
            page = 1
        if page <= 0:
            page = 1

        print(page)
        start = (page - 1) * 5
        end = page * 5
        current_list = LIST_INFO[start:end]
        self.render("home/index.html", list_info=current_list,current_page=page)

    def post(self,page):
        username = self.get_argument("username", "")
        email = self.get_argument("email", "")

        tmp = {"username": username, "email": email}
        LIST_INFO.append(tmp)
        self.redirect("/index/"+page)
#!/usr/bin/env python
# coding=utf-8
import time
import tornado.ioloop
import tornado.web


# 业务逻辑处理模块


# 配置选项模块
from controllers import home


# 静态文件和模板文件的配置
settings = {
    'template_path': 'templates',
    'static_path': 'statics',
}

# 路由模块
## 动态路由系统
application = tornado.web.Application([
    (r"/index/(?P<page>d*)", home.IndexHandler),
],
    **settings
)

## wsgi模块
if __name__ == "__main__":
    application.listen(8888)
    tornado.ioloop.IOLoop.instance().start()
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>index</title>
</head>
<body>
<h1>添加数据</h1>
<form action="/index/{{ current_page }}" method="post">
    <input type="text" name="username">
    <input type="email" name="email">
    <input type="submit" typeof="提交">
</form>
<h1>显示数据</h1>
<table border="1">
    <thead>
        <tr>
            <th>用户名</th>
            <th>邮箱</th>
        </tr>
    </thead>
    <tbody>
        {% for line in list_info %}
            <tr>
                <td>{{ line['username'] }}</td>
                <td>{{ line['email'] }}</td>
            </tr>
        {% end %}
    </tbody>
</table>
</body>
</html>

xss跨站

效果: 提交js代码.每刷新一次都会执行一次.

未分类

实现:让代码按照原格式执行,前面加raw

    <tbody>
        {% for line in list_info %}
            <tr>
                <!--<td>{{ line['username'] }}</td>-->
                <td>{% raw line['username'] %}</td>
                <td>{{ line['email'] }}</td>
            </tr>
        {% end %}
    </tbody>

初步实现点分页链接

效果:

未分类

核心代码

<table border="1">
    <thead>
        <tr>
            <th>用户名</th>
            <th>邮箱</th>
        </tr>
    </thead>
    <tbody>
        {% for line in list_info %}
            <tr>
                <!--<td>{{ line['username'] }}</td>-->
                <td>{% raw line['username'] %}</td>
                <td>{{ line['email'] }}</td>
            </tr>
        {% end %}
    </tbody>
</table>
<div class = "pager">
    <a href="/index/1">1</a>
    <a href="/index/2">2</a>
    <a href="/index/3">3</a>
</div>

从后端传值过来

实现效果

未分类

    def get(self, page):
        try:
            page = int(page)
        except:
            page = 1
        if page <= 0:
            page = 1


        start = (page - 1) * 5
        end = page * 5
        current_list = LIST_INFO[start:end]

        all_pager,c = divmod(len(LIST_INFO),5)
        if c>0:
            all_pager+=1

        str_page = """
        <a href="/index/1">1</a>
        <a href="/index/2">2</a>
        <a href="/index/3">3</a>
        <a href="/index/4">4</a>
        """
        self.render("home/index.html", list_info=current_list,current_page=page,str_page=str_page)

实现分页链接根据后端数据长度自动生成

效果图:

未分类

核心代码:

LIST_INFO = [
    {"username": "maotai", "email": "123456"},
]
for line in range(300):
    tmp={"username": "maotai", "email": "123456"}
    LIST_INFO.append(tmp)
    def get(self, page):
        try:
            page = int(page)
        except:
            page = 1
        if page <= 0:
            page = 1


        start = (page - 1) * 5
        end = page * 5
        current_list = LIST_INFO[start:end]

        all_pager,c = divmod(len(LIST_INFO),5)
        if c>0:
            all_pager+=1

        # str_page = """
        # <a href="/index/1">1</a>
        # <a href="/index/2">2</a>
        # <a href="/index/3">3</a>
        # <a href="/index/4">4</a>
        # """
        list_page=[]
        for p in range(all_pager):
            tmp = '<a href="/index/%s">%s</a>'%(p+1,p+1)
            list_page.append(tmp)
        str_page="".join(list_page)
        self.render("home/index.html", list_info=current_list,current_page=page,str_page=str_page)
<div class = "pager">
    <!--{{str_page}}-->
    {% raw str_page %}
</div>

实现当前页分页链接高亮

未分类

    def get(self, page):
        try:
            page = int(page)
        except:
            page = 1
        if page <= 0:
            page = 1


        start = (page - 1) * 5
        end = page * 5
        current_list = LIST_INFO[start:end]

        all_pager,c = divmod(len(LIST_INFO),5)
        if c>0:
            all_pager+=1

        # str_page = """
        # <a href="/index/1">1</a>
        # <a href="/index/2">2</a>
        # <a href="/index/3">3</a>
        # <a href="/index/4">4</a>
        # """
        list_page=[]
        for p in range(all_pager):
            if p+1 == page:
                tmp = '<a class="active" href="/index/%s">%s</a>' % (p + 1, p + 1)
            else:
                tmp = '<a href="/index/%s">%s</a>'%(p+1,p+1)
            list_page.append(tmp)
        str_page="".join(list_page)
        self.render("home/index.html", list_info=current_list,current_page=page,str_page=str_page)

前端:

        .pager a.active{
            background-color: brown;
            color: white;
        }

完整的code:

未分类

#!/usr/bin/env python
# coding=utf-8
import tornado.web

LIST_INFO = [
    {"username": "maotai", "email": "123456"},
]
for line in range(300):
    tmp={"username": "maotai", "email": "123456"}
    LIST_INFO.append(tmp)

class IndexHandler(tornado.web.RequestHandler):
    # def get(self, *args, **kwargs):
    def get(self, page):
        try:
            page = int(page)
        except:
            page = 1
        if page <= 0:
            page = 1


        start = (page - 1) * 5
        end = page * 5
        current_list = LIST_INFO[start:end]

        all_pager,c = divmod(len(LIST_INFO),5)
        if c>0:
            all_pager+=1

        # str_page = """
        # <a href="/index/1">1</a>
        # <a href="/index/2">2</a>
        # <a href="/index/3">3</a>
        # <a href="/index/4">4</a>
        # """
        list_page=[]
        for p in range(all_pager):
            if p+1 == page: #这里为什么p+1,因为遍历的是从0到all_pager
                tmp = '<a class="active" href="/index/%s">%s</a>' % (p + 1, p + 1)
            else:
                tmp = '<a href="/index/%s">%s</a>'%(p+1,p+1)
            list_page.append(tmp)
        str_page="".join(list_page)
        self.render("home/index.html", list_info=current_list,current_page=page,str_page=str_page)

    def post(self,page):
        username = self.get_argument("username", "")
        email = self.get_argument("email", "")

        tmp = {"username": username, "email": email}
        LIST_INFO.append(tmp)
        self.redirect("/index/"+page)
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>index</title>
    <style>
        .pager a{
            display: inline-block;
            padding:5px;
            margin: 3px;
            background-color: cadetblue;
        }
        .pager a.active{
            background-color: brown;
            color: white;
        }
    </style>
</head>
<body>
<h1>添加数据</h1>
<form action="/index/{{ current_page }}" method="post">
    <input type="text" name="username">
    <input type="email" name="email">
    <input type="submit" typeof="提交">
</form>
<h1>显示数据</h1>
<table border="1">
    <thead>
        <tr>
            <th>用户名</th>
            <th>邮箱</th>
        </tr>
    </thead>
    <tbody>
        {% for line in list_info %}
            <tr>
                <td>{{ line['username'] }}</td>
                <!--<td>{% raw line['username'] %}</td>-->
                <td>{{ line['email'] }}</td>
            </tr>
        {% end %}
    </tbody>
</table>
<div class = "pager">
    <!--{{str_page}}-->
    {% raw str_page %}
</div>
</body>
</html>

实现显示11项,当前页+-5

未分类

分析:

当总页数<11:
    1 11
当总页数>11:
    当前页<6:
        1 11
    当前页>6:
        当前页+5>总页数
            总页数-11 总页数
        当前页+5<总页数:
            s=page-5
            t=page+5

核心实现代码:

class IndexHandler(tornado.web.RequestHandler):
    # def get(self, *args, **kwargs):
    def get(self, page):
        try:
            page = int(page)
        except:
            page = 1
        if page <= 0:
            page = 1

        start = (page - 1) * 5
        end = page * 5
        current_list = LIST_INFO[start:end]

        all_pager, c = divmod(len(LIST_INFO), 5)
        if c > 0:
            all_pager += 1

        # str_page = """
        # <a href="/index/1">1</a>
        # <a href="/index/2">2</a>
        # <a href="/index/3">3</a>
        # <a href="/index/4">4</a>
        # """
        list_page = []
        if all_pager < 11:
            s = 1
            t = 11
        else:
            if page <= 6:
                s = 1
                t = 11
            elif page > 6:
                if page + 5 > all_pager:
                    s = all_pager - 11
                    t = all_pager
                else:
                    s = page - 5
                    t = page + 5

        for p in range(s, t + 1):
            if p == page:  # 这里为什么p,而不是p+1? 因为range(s, t + 1),不是从0开始的.
                tmp = '<a class="active" href="/index/%s">%s</a>' % (p, p)
            else:
                tmp = '<a href="/index/%s">%s</a>' % (p, p)
            list_page.append(tmp)
        str_page = "".join(list_page)
        self.render("home/index.html", list_info=current_list, current_page=page, str_page=str_page)

封装分页class

思路

未分类

代码:

#!/usr/bin/env python
# coding=utf-8
import tornado.web

LIST_INFO = [
    {"username": "maotai", "email": "123456"},
]
for line in range(300):
    tmp = {"username": "maotai", "email": "123456"}
    LIST_INFO.append(tmp)


class Pagination:
    def __init__(self, current_page, all_item):
        all_pager, c = divmod(len(all_item), 5)
        if c > 0:
            all_pager += 1
        try:
            current_page = int(current_page)
        except:
            current_page = 1
        if current_page <= 0:
            current_page = 1

        self.current_page = current_page
        self.all_pager = all_pager

    @property
    def start(self):
        return (self.current_page - 1) * 5

    @property
    def end(self):
        return self.current_page * 5

    def page_str(self, baseurl):
        list_page = []
        if self.all_pager < 11:
            s = 1
            t = 11
        else:
            if self.current_page <= 6:
                s = 1
                t = 11
            elif self.current_page > 6:
                if self.current_page + 5 > self.all_pager:
                    s = self.all_pager - 11
                    t = self.all_pager
                else:
                    s = self.current_page - 5
                    t = self.current_page + 5

        for p in range(s, t + 1):
            if p == self.current_page:
                tmp = '<a class="active" href="%s%s">%s</a>' % (baseurl, p, p)
            else:
                tmp = '<a href="%s%s">%s</a>' % (baseurl, p, p)
            list_page.append(tmp)
        return "".join(list_page)

    def current_list_info(self):
        current_list = LIST_INFO[self.start:self.end]
        return current_list


class IndexHandler(tornado.web.RequestHandler):
    # def get(self, *args, **kwargs):
    def get(self, page):
        ## 实例化分页类
        # 传入当前页 和 所有数据
        # 获得current_list 即每页5条数据列表
        page_obj = Pagination(page, LIST_INFO)
        current_list = page_obj.current_list_info()
        str_page = page_obj.page_str("/index/")
        self.render("home/index.html", list_info=current_list, current_page=page, str_page=str_page)

    def post(self, page):
        username = self.get_argument("username", "")
        email = self.get_argument("email", "")

        tmp = {"username": username, "email": email}
        LIST_INFO.append(tmp)
        self.redirect("/index/" + page)

实现上一页 下一页 跳转到

效果:

未分类

原理:

未分类

利用js的方法href实现跳转.

完整的code:

#!/usr/bin/env python
# coding=utf-8
import tornado.web

LIST_INFO = [
    {"username": "maotai", "email": "123456"},
]
for line in range(300):
    tmp = {"username": "maotai", "email": "123456"}
    LIST_INFO.append(tmp)


class Pagination:
    def __init__(self, current_page, all_item):
        all_pager, c = divmod(len(all_item), 5)
        if c > 0:
            all_pager += 1
        try:
            current_page = int(current_page)
        except:
            current_page = 1
        if current_page <= 0:
            current_page = 1

        self.current_page = current_page
        self.all_pager = all_pager
        self.all_item=all_item

    @property
    def start(self):
        return (self.current_page - 1) * 5

    @property
    def end(self):
        return self.current_page * 5

    def current_list_info(self):
        current_list = LIST_INFO[self.start:self.end]
        return current_list

    def page_str(self, baseurl):
        list_page = []

        # 上一页  --上一页 页码 下一页 必须按照顺序添加到list
        if self.current_page > 1:
            last_page = '<a href="%s%s">上一页</a>' % (baseurl, self.current_page - 1)
        else:
            last_page = '<a href="%s%s">上一页</a>' % (baseurl, self.current_page)
        list_page.append(last_page)

        # 页码页
        if self.all_pager < 11:
            s = 1
            t = 11
        else:
            if self.current_page <= 6:
                s = 1
                t = 11
            elif self.current_page > 6:
                if self.current_page + 5 > self.all_pager:
                    s = self.all_pager - 11
                    t = self.all_pager
                else:
                    s = self.current_page - 5
                    t = self.current_page + 5

        for p in range(s, t + 1):

            if p == self.current_page:
                tmp = '<a class="active" href="%s%s">%s</a>' % (baseurl, p, p)
            else:
                tmp = '<a href="%s%s">%s</a>' % (baseurl, p, p)
            list_page.append(tmp)

        # if self.current_page>1:
        #     syyp = self.current_page - 1
        # else:
        #     syyp = self.current_page
        #
        # if self.current_page > len(self.all_item):
        #     xyyp = len(self.all_item)
        # else:
        #     xyyp = self.current_page
        #
        # syy = '<a href="%s%s">上一页</a>' % (baseurl, syyp)
        # xyy = '<a href="%s%s">下一页</a>' % (baseurl, xyyp)
        # list_page.insert(0,syy)
        # list_page.append(xyy)



        # 下一页
        if self.current_page< self.all_pager:
            next_page = '<a href="%s%s">下一页</a>' % (baseurl, self.current_page+1)
        else:
            next_page = '<a href="%s%s">下一页</a>' % (baseurl, self.current_page)
        list_page.append(next_page)

        # 跳转到
        ## 获取本次点击前框里输入的数据, 拼接 baseurl + 数字 就可以实现跳转
        jump = """<input type="text" /><a onclick="Jump('%s',this);">GO</a>"""%(baseurl,)
        script="""<script>
            function Jump(baseUrl,ths){
                var val = ths.previousElementSibling.value;
                if(val.trim().length>0){
                    location.href = baseUrl+val;
                }
            }
            </script>
        """
        list_page.append(jump)
        list_page.append(script)
        return "".join(list_page)


class IndexHandler(tornado.web.RequestHandler):
    # def get(self, *args, **kwargs):
    def get(self, page):
        page_obj = Pagination(page, LIST_INFO)
        current_list = page_obj.current_list_info()
        str_page = page_obj.page_str("/index/")
        self.render("home/index.html", list_info=current_list, current_page=page, str_page=str_page)

    def post(self, page):
        username = self.get_argument("username", "")
        email = self.get_argument("email", "")

        tmp = {"username": username, "email": email}
        LIST_INFO.append(tmp)
        self.redirect("/index/" + page)

Flask连接mysql数据库初级到进阶过程

未分类

前言

在初学 Flask 的时候,在数据库连接这部分也跟每个初学者一样。但是随着工作中项目接手的多了,代码写的多了,历练的多了也就有了自己的经验和技巧。在对这块儿代码不断的进行升级改造后,整理了自己在连接数据库这部分的的一个学习经验,也就是我们今天分享的连接数据库部分的打怪升级之旅。希望可以为大家在学习 Python 的路上提供一些参考。

初级阶段

首先安装 Mysql 扩展包

未分类

建立数据库链接

未分类

开启打怪升级之路

在日常开发中,连接数据库最多的应用场景就是,查询所有数据和查询单条数据。就以查询所有数据场景为例。

小白版本——在后端凭接表格,传到前端渲染

未分类

进阶阶段

第一关——后端消灭 HTML 标签

后端:

未分类

前端:

未分类

第二关——让返回值更优雅

未分类

第三关——让代码更简洁

未分类

一个更高效的方式——直接将返回的嵌套元祖转换为嵌套的字典,常用与只查询 ID, Username 的场景

未分类

经验总结

作为一个程序员学习新的技术知识都是必须的,我们都是自己事业上无人可替的开拓者,我们都是要经历从入门到熟练再到精略的过程,过程虽然很痛苦不过收获的喜悦也是别人羡慕不来的,IT 大牛 不是那么容易就练成的。希望今天的分享能够帮助到大家。

docker容器内通过supervisor来守护进程

安装:

可通过easy install supervisor或pip install supervisor,当然还可以通过下载源码通过Python setup.py install 来安装(注意:要在python2.x下进行安装)

还可以通过linxu下的包管理来安装,如yum install supervisor

使用:

.为要维护的进程创建.ini文件,并放到/etc/supervisor.d目录下
.启动supervisord服务
/usr/bin/supervisord -c /etc/supervisor.conf

可通过supervisorctl status 查看supervisord当前管理的所有进程的状态

遇到的问题:

通过docker run -d 方式启动容器报“Unlinking stale socket /tmp/supervisor.sock”错误,而通过docker run -it 启动后手动执行 /usr/bin/supervisord -c /etc/supervisor.conf则没问题

解决:

将Dockerfile中的CMD [“/usr/bin/supervisord”, “-c”, “/etc/supervisord.conf”] 修改成ENTRYPOINT [“/usr/bin/supervisord”, “-n”, “-c”, “/etc/supervisord.conf”] 重新生成镜像,用该镜像启动容器docker run -d即可,问题解决。

postgresql的远程链接出错原因分析

在写一个在线编译系统时打算用远程的数据库进行测试,但是链接出现了问题.
这里先说一下远程连接时需要修改的配置吧(只是针对Linux用户).

远程链接配置

需要修改pg_hba.conf和postgresql.conf这两个文件.
PS:如果你在安装数据库时,在进行数据库初始化操作(以centos6.X版本为例),如下:

sudo service postgresql-9.5 initdb

在初始化数据库时可以指定参数 –PGDATA=“/data”,该参数是用于指明数据库的数据文件的存放路径,默认是在/var/lib/pgsql/9.5/data路径下。

# TYPE  DATABASE        USER            ADDRESS                 METHOD

# "local" is for Unix domain socket connections only
local   all             all                                     ident
# IPv4 local connections:
host    all             all             127.0.0.1/32            ident
# IPv6 local connections:
host    all             all             ::1/128                 ident
# Allow replication connections from localhost, by a user with the
# replication privilege.
#local   replication     postgres                                peer
#host    replication     postgres        127.0.0.1/32            ident
#host    replication     postgres        ::1/128                 ident

刚刚安装完成的postgresql是这样的.
只是针对自己本机登录识别.

需要修改如下:

# "local" is for Unix domain socket connections only
local   all             all                                     trust
# IPv4 local connections:
host    all             all             127.0.0.1/32            trust
host    all             all             0.0.0.0/24              trust
# IPv6 local connections:
host    all             all             ::1/128                 trust

另一个要改的就是postgresql.conf文件.
将listen_address =”localhost” 引号里面改为*,即对所有地址监听.

原因

在ipv4下面添加的0.0.0.0/24这个是对全网段的许可,24表示的是子网掩码.
这里很多人都将后面的ident改为MD5的认证方式,导致FATAL: password authentication failed for user “postgres”错误的出现.
其实这个错误的根本原因就是:用户密码认证错误引起的,因为postgresql安装完成之后,默认的就是md5的认证方式,正确的方式就是直接时候trust就ok了.

希望以上对你有帮助.

Linux(CentOS)下Postgresql数据库的安装配置

1. 验证本机是否已经安装:

[root@master ~]# rpm -qa | grep postgresql

2. yum方式加-y参数安装(-y默认需要选择的全部选择是)

[root@master ~]# yum -y install postgresql*

3.初始化数据库

[root@master ~]# /etc/rc.d/init.d/postgresql initdb

Initializing database: [ OK ]

4. 启动postgresql数据库

[root@master ~]# /etc/rc.d/init.d/postgresql start
Starting postgresql service: [ OK ]

5. 查看默认postgresql数据库的默认端口是否正常启动

[root@master ~]# ps -ef |grep 5432
postgres 14517 1 0 00:21 ? 00:00:00 /usr/bin/postmaster -p 5432 -D /var/lib/pgsql/data

6. 对postgres数据库账号修改密码

执行ln建立软链接,否则可能会报下面的错误

[root@master ~]# psql
psql: could not connect to server: No such file or directory
Is the server running locally and accepting
connections on Unix domain socket "/var/run/postgresql/.s.PGSQL.5432"?
[root@master ~]# sudo ln -s /tmp/.s.PGSQL.5432 /var/run/postgresql/.s.PGSQL.5432
[root@master ~]# su postgres
bash-4.1$ psql
psql (8.4.20)
Type "help" for help.
postgres=# ALTER USER postgres WITH PASSWORD 'postgres';

7. 修改pg_hba.conf配置文件中的认证方式为md5(最后几行对应ident改为md5)

[root@master ~]# vi /var/lib/pgsql/data/pg_hba.conf 
# /var/lib/pgsql/data是RedHat或CentOS下默认安装目录

8. 重启postgres数据库服务,重新加载配置文件信息

[root@master ~]# /etc/rc.d/init.d/postgresql restart
Stopping postgresql service: [ OK ]
Starting postgresql service: [ OK ]
[root@master ~]# psql -U postgres
Password for user postgres: 
psql (8.4.20)
Type "help" for help.
postgres=#

9. 创建测试数据库并查看所有数据库信息

postgres=# create database pg_test_db;
CREATE DATABASE
postgres=# select *from pg_database;

10. 打开扩展显示,类似mysql里面的G参数,使返回结果竖着显示

postgres=# x
Expanded display is on.

--ps auxw | grep post

11. 修改postgresql.conf和pg_hba.conf配置参数

listen_addresses 
  • 数据库名与用户名注意大小写,PostgreSQL是区分大小的;
  • CIDR-ADDRESS(用户的IP范围)的值的格式是:IP/数值
    数值取值范围为:0,8,16,24,32 即表示掩码bit的个数;

例如:

  • 32 -> 192.168.1.1/32 表示必须是来自这个IP地址的访问才合法;
  • 24 -> 192.168.1.0/24 表示只要来自192.168.1.0 ~ 192.168.1.255的都合法;
  • 16 -> 192.168.0.0/16 表示只要来自192.168.0.0 ~ 192.168.255.255的都合法;
  • 8 -> 192.0.0.0/16 表示只要来自192.0.0.0 ~ 192.255.255.255的都合法;
  • 0 -> 0.0.0.0/0 表示全部IP地址都合法,/左边的IP地址随便了只要是合法的IP地址即可;

12. 内存参数的设置

PostgreSQL安装完毕后,可以修改一下两个主要的内存参数。

  • shared_buffers:共享内存的大小,主要用于共享数据块
  • work_men:单个SQL执行时,排序、hash join所使用的内存,SQL运行完后,内存就释放了。
  • shared_buffers默认值为32MB,work_men为1MB,如果你的机器上有足够的内存,可以把这个参数改得大一些,这样数据库就可以缓存更多的数据块,当读取数据时,就可以从共享内存中读,而不需要再从文件上去读取。
  • work_men设置大一些,会让排序操作更快一些。

docker 容器基础技术:linux cgroup 简介

Linux cgroups 的全称是 Linux Control Groups,它是 Linux 内核的特性,主要作用是限制、记录和隔离进程组(process groups)使用的物理资源(cpu、memory、IO 等)。

2006 的时候,Google 的一些工程师(主要是 Paul Menage 和 Rohit Seth)启动了这个项目,最初的名字叫 process containers。因为 container 在内核中名字有歧义,2007 的时候改名为 control groups,并合并到 2008 年发布的 2.6.24 内核版本。

最初 cgroups 的版本被称为 v1,这个版本的 cgroups 设计并不友好,理解起来非常困难。后续的开发工作由 Tejun Heo 接管,他重新设计并重写了 cgroups,新版本被称为 v2,并首次出现在 kernel 4.5 版本。

cgroups 从设计之初使命就很明确,为进程提供资源控制,它主要的功能包括:

  • 资源限制:限制进程使用的资源上限,比如最大内存、文件系统缓存使用限制
  • 优先级控制:不同的组可以有不同的优先级,比如 CPU 使用和磁盘 IO 吞吐
  • 审计:计算 group 的资源使用情况,可以用来计费
  • 控制:挂起一组进程,或者重启一组进程

目前 cgroups 已经成为很多技术的基础,比如 LXC、docker、systemd等。

NOTE:资源限制是这篇文章的重点,也是 docker 等容器技术的基础。其他特性可以参考内核 cgroups 文档。

cgroups 核心概念

前面说过,cgroups 是用来对进程进行资源管理的,因此 cgroup 需要考虑如何抽象这两种概念:进程和资源,同时如何组织自己的结构。cgroups 中有几个非常重要的概念:

  • task:任务,对应于系统中运行的一个实体,一般是指进程
  • subsystem:子系统,具体的资源控制器(resource class 或者 resource controller),控制某个特定的资源使用。比如 CPU 子系统可以控制 CPU 时间,memory 子系统可以控制内存使用量
  • cgroup:控制组,一组任务和子系统的关联关系,表示对这些任务进行怎样的资源管理策略
  • hierarchy:层级树,一系列 cgroup 组成的树形结构。每个节点都是一个 cgroup,cgroup 可以有多个子节点,子节点默认会继承父节点的属性。系统中可以有多个 hierarchy

虽然 cgroups 支持 hierarchy,允许不同的子资源挂到不同的目录,但是多个树之间有各种限制,增加了理解和维护的复杂性。在实际使用中,所有的子资源都会统一放到某个路径下(比如 ubuntu16.04 的 /sys/fs/cgroup/),因此本文并不详细介绍多个树的情况,感兴趣的可以参考 RedHat 的这篇文档。

子资源系统(Resource Classes or SubSystem)

目前有下面这些资源子系统:

  • Block IO(blkio):限制块设备(磁盘、SSD、USB 等)的 IO 速率
  • CPU Set(cpuset):限制任务能运行在哪些 CPU 核上
  • CPU Accounting(cpuacct):生成 cgroup 中任务使用 CPU 的报告
  • CPU (CPU):限制调度器分配的 CPU 时间
  • Devices (devices):允许或者拒绝 cgroup 中任务对设备的访问
  • Freezer (freezer):挂起或者重启 cgroup 中的任务
  • Memory (memory):限制 cgroup 中任务使用内存的量,并生成任务当前内存的使用情况报告
  • Network Classifier(net_cls):为 cgroup 中的报文设置上特定的 classid 标志,这样 tc 等工具就能根据标记对网络进行配置
  • Network Priority (net_prio):对每个网络接口设置报文的优先级
  • perf_event:识别任务的 cgroup 成员,可以用来做性能分析

Hierarchy

Linux 进程之间组成一棵树的结构,每个进程(除了 init 根进程之外)都有一个父进程,子进程创建之后会继承父进程的一些属性(比如环境变量,打开的文件描述符等)。

和进程模型类似,只不过 cgroups 是一个森林结构。

使用 cgroups

cgroup 内核功能比较有趣的地方是它没有提供任何的系统调用接口,而是通过文件系统来操作,cgroup 实现了一个

使用 cgroups 的方式有几种:

  • 使用 cgroups 提供的虚拟文件系统,直接通过创建、读写和删除目录、文件来控制 cgroups
  • 使用命令行工具,比如 libcgroup 包提供的 cgcreate、cgexec、cgclassify 命令
  • 使用 rules engine daemon 提供的配置文件
  • 当然,systemd、lxc、docker 这些封装了 cgroups 的软件也能让你通过它们定义的接口控制 cgroups 的内容

直接操作 cgroup 文件系统

查看 cgroups 挂载信息

在 ubuntu 16.04 的机器上,cgroups 已经挂载到文件系统上了,可以通过 mount 命令查看:

➜  ~ mount -t cgroup
cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,xattr,release_agent=/lib/systemd/systemd-cgroups-agent,name=systemd)
cgroup on /sys/fs/cgroup/net_cls,net_prio type cgroup (rw,nosuid,nodev,noexec,relatime,net_cls,net_prio)
cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,cpu,cpuacct)
cgroup on /sys/fs/cgroup/hugetlb type cgroup (rw,nosuid,nodev,noexec,relatime,hugetlb,release_agent=/run/cgmanager/agents/cgm-release-agent.hugetlb)
cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,cpuset,clone_children)
cgroup on /sys/fs/cgroup/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,freezer)
cgroup on /sys/fs/cgroup/perf_event type cgroup (rw,nosuid,nodev,noexec,relatime,perf_event,release_agent=/run/cgmanager/agents/cgm-release-agent.perf_event)
cgroup on /sys/fs/cgroup/pids type cgroup (rw,nosuid,nodev,noexec,relatime,pids,release_agent=/run/cgmanager/agents/cgm-release-agent.pids)
cgroup on /sys/fs/cgroup/devices type cgroup (rw,nosuid,nodev,noexec,relatime,devices)
cgroup on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,blkio)
cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory)

如果没有的话,也可以通过以下命令来把想要的 subsystem mount 到系统中:

$ mount -t cgroup -o cpu,cpuset,memory cpu_and_mem /cgroup/cpu_and_mem

上述命令表示把 cpu、cpuset、memory 三个子资源 mount 到 /cgroup/cpu_and_mem 目录下。

每个 cgroup 目录下面都会有描述该 cgroup 的文件,除了每个 cgroup 独特的资源控制文件,还有一些通用的文件:

  • tasks:当前 cgroup 包含的任务(task)pid 列表,把某个进程的 pid 添加到这个文件中就等于把进程移到该 cgroup
  • cgroup.procs:当前 cgroup 中包含的 thread group 列表,使用逻辑和 tasks 相同
  • notify_on_release:0 或者 1,是否在 cgroup 销毁的时候执行 notify。如果为 1,那么当这个 cgroup 最后一个任务离开时(退出或者迁移到其他 cgroup),并且最后一个子 cgroup 被删除时,系统会执行 release_agent 中指定的命令
  • release_agent:需要执行的命令

创建 cgroup

创建 cgroup,可以直接用 mkdir 在对应的子资源中创建一个目录:

➜  ~ mkdir /sys/fs/cgroup/cpu/mycgroup
➜  ~ ls /sys/fs/cgroup/cpu/mycgroup
cgroup.clone_children  cpuacct.stat   cpuacct.usage_percpu  cpu.cfs_quota_us  cpu.stat           tasks
cgroup.procs           cpuacct.usage  cpu.cfs_period_us     cpu.shares        notify_on_release

上面命令在 cpu 子资源中创建了 mycgroup,创建 cgroup 之后,目录中会自动创建需要的文件。我们后面会详细讲解这些文件的含义,目前只需要知道它们能够控制对应子资源就行。

删除 cgroup

删除子资源,就是删除对应的目录:

$ rmdir /sys/fs/cgroup/cpu/mycgroup/

删除之后,如果 tasks 文件中有进程,它们会自动迁移到父 cgroup 中。

设置 cgroup 参数

设置 group 的参数就是直接往特定的文件中写入特定格式的内容,比如要限制 cgroup 能够使用的 CPU 核数:

$ echo 0-1 > /sys/fs/cgroup/cpuset/mycgroup/cpuset.cpus

把进程加入到 cgroup

要把某个已经运行的进程加入到 cgroup,可以直接往需要的 cgroup tasks 文件中写入进程的 PID:

$ echo 2358 > /sys/fs/cgroup/memory/mycgroup/tasks

在 cgroup 中运行进程

如果想直接把进程运行在某个 cgroup,但是运行前还不知道进程的 Pid 应该怎么办呢?

我们可以利用 cgroup 的继承方式来实现,因为子进程会继承父进程的 cgroup,因此我们可以把当前 shell 加入到要想的 cgroup:

echo $$ > /sys/fs/cgroup/cpu/mycgroup/tasks

上面的方案有个缺陷,运行完之后原来的 shell 还在 cgroup 中。如果希望进程运行完不影响当前使用的 shell,可以另起一个临时的 shell:

sh -c "echo $$ > /sys/fs/cgroup/memory/mycgroup/tasks & & stress -m 1"

把进程移动到 cgroup

如果想要把进程移动到另外一个 cgroup,只要使用 echo 把进程 PID 写入到 cgroup tasks 文件中即可,原来 cgroup tasks 文件会自动删除该进程。

cgroup-tools

cgroup-tools 软件包提供了一系列命令可以操作和管理 cgroup,ubuntu 系统中可以通过下面的命令安装:

sudo apt-get install -y cgroup-tools

列出 cgroup mount 信息

最简单的,lssubsys 可以查看系统中存在的 subsystems:

➜  ~ lssubsys -am
cpuset /sys/fs/cgroup/cpuset
cpu,cpuacct /sys/fs/cgroup/cpu,cpuacct
blkio /sys/fs/cgroup/blkio
memory /sys/fs/cgroup/memory
devices /sys/fs/cgroup/devices
freezer /sys/fs/cgroup/freezer
net_cls,net_prio /sys/fs/cgroup/net_cls,net_prio
perf_event /sys/fs/cgroup/perf_event
hugetlb /sys/fs/cgroup/hugetlb
pids /sys/fs/cgroup/pids

创建 cgroup

cgcreate 可以用来为用户创建指定的 cgroups:

➜  sudo cgcreate -a cizixs -t cizixs -g cpu,memory:test1 
➜  ls cpu/test1 
cgroup.clone_children  cpuacct.stat   cpuacct.usage_all     cpuacct.usage_percpu_sys   cpuacct.usage_sys   cpu.cfs_period_us  cpu.shares  notify_on_release
cgroup.procs           cpuacct.usage  cpuacct.usage_percpu  cpuacct.usage_percpu_user  cpuacct.usage_user  cpu.cfs_quota_us   cpu.stat    tasks

上面的命令表示在 /sys/fs/cgroup/cpu 和 /sys/fs/cgroup/memory 目录下面分别创建 test1 目录,也就是为 cpu 和 memory 子资源创建对应的 cgroup。

  • 选项 -t 指定 tasks 文件的用户和组,也就是指定哪些人可以把任务添加到 cgroup 中,默认是从父 cgroup 继承
  • -a 指定除了 tasks 之外所有文件(资源控制文件)的用户和组,也就是哪些人可以管理资源参数
  • -g 指定要添加的 cgroup,冒号前是逗号分割的子资源类型,冒号后面是 cgroup 的路径(这个路径会添加到对应资源 mount 到的目录后面)。也就是说在特定目录下面添加指定的子资源

删除 cgroup

知道怎么创建,也要知道怎么删除。不然系统中保留着太多用不到的 cgroup 浪费系统资源,也会让管理很麻烦。

cgdelete 可以删除对应的 cgroups,它和 cgcreate 命令类似,可以用 -g 指定要删除的 cgroup:

➜  cgroup sudo cgdelete -g cpu,memory:test1
cgdelete 也提供了 -r 参数可以递归地删除某个 cgroup 以及它所有的子 cgroup。

如果被删除的 cgroup 中有任务,这些任务会自动移到父 cgroup 中。

设置 cgroup 的参数

cgset 命令可以设置某个子资源的参数,比如如果要限制某个 cgroup 中任务能使用的 CPU 核数:

$ cgset -r cpuset.cpus=0-1 /mycgroup

-r 后面跟着参数的键值对,每个子资源能够配置的键值对都有自己的规定,我们会在后面详细解释。

cgset 还能够把一个 cgroup 的参数拷贝到另外一个 cgroup 中:

$ cgset --copy-from group1/ group2/

NOTE: cgset 如果设置没有成功也不会报错,请一定要注意。

在某个 cgroup 中运行进程

cgexec 执行某个程序,并把程序添加到对应的 cgroups 中:

➜  cgroup cgexec -g memory,cpu:cizixs bash

cgroups 是可以有层级结构的,因此可以直接创建具有层级关系的 cgroup,然后运行在该 cgroup 中:

$ cgcreate -g memory,cpu:groupname/foo
$ cgexec -g memory,cpu:groupname/foo bash

把已经运行的进程移动到某个 cgroup

要把某个已经存在的程序(能够知道它的 pid)移到某个 cgroup,可以使用 cgclassify 命令:

比如把当前 bash shell 移入到特定的 cgroup 中

$ cgclassify -g memory,cpu:/mycgroup $$

$$ 表示当前进程的 pid 号,上面命令可以方便地测试一些耗费内存或者 CPU 的进程,如果 /mycgroup 对 CPU 和 memory 做了限制。

这个命令也可以同时移动多个进程,它们 pid 之间用空格隔开:

$ cgclassify -g cpu,memory:group1 1701 1138

cgroup 子资源参数详解

每个 subssytem 负责系统的一部分资源管理,又分别提供多个参数可以控制,每个参数对应一个文件,往文件中写入特定格式的内容就能控制该资源。

blkio:限制设备 IO 访问

限制磁盘 IO 有两种方式:权重(weight)和上限(limit)。权重是给不同的应用(或者 cgroup)一个权重值,各个应用按照百分比来使用 IO 资源;上限是直接写死应用读写速率的最大值。

设置 cgroup 访问设备的权重:

设置的权重并不能保证什么,当只有某个应用在读写磁盘时,不管它权重多少,都能使用磁盘。只有当多个应用同时读写磁盘时,才会根据权重为应用分配读写的速率。

  • blkio.weight:设置 cgroup 读写设备的权重,取值范围在 100-1000
  • blkio.weight_device:设置 cgroup 使用某个设备的权重。当访问该设备时,它会使用当前值,覆盖 blkio.weight 的值。内容的格式为 major:minor weight,前面是设备的 major 和 minor 编号,用来唯一表示一个设备,后面是 100-1000 之间的整数值。设备号的分配可以参考:https://www.kernel.org/doc/html/v4.11/admin-guide/devices.html

设置 cgroup 访问设备的限制:

除了设置权重之外,还能设置 cgroup 磁盘的使用上限,保证 cgroup 中的进程读写磁盘的速率不会超过某个值。

  • blkio.throttle.read_bps_device:最多每秒钟从设备读取多少字节
  • blkio.throttle.read_iops_device:最多每秒钟从设备中执行多少次读操作
  • blkio.throttle.write_bps_device:最多每秒钟可以往设备写入多少字节
  • blkio.throttle.write_iops_device:最多每秒钟可以往设备执行多少次写操作

读写字节数的限制格式一样 major:minor bytes_per_second,前面两个数字代表某个设备,后面跟着一个整数,代表每秒读写的字节数,单位为比特,如果需要其他单位(KB、MB等)需要自行转换。比如要限制 /dev/sda 读速率上线为 10 Mbps,可以运行:

$ echo "8:0 10485760" >
/sys/fs/cgroup/blkio/mygroup/blkio.throttle.read_bps_device

iops 代表 IO per second,是每秒钟执行读写的次数,格式为 major:minor operations_per_second。比如,要限制每秒只能写 10 次,可以运行:

$ echo "8:0 10" >
/sys/fs/cgroup/blkio/mygroup/blkio.throttle.write_iops_device

除了限制磁盘使用之外,blkio 还提供了 throttle 规则下磁盘使用的统计数据。

  • blkio.throttle.io_serviced:cgroup 中进程读写磁盘的次数,文件中内容格式为 major:minor operation number,表示对磁盘进行某种操作(read、write、sync、async、total)的次数
  • blkio.throttle.io_service_bytes:和上面类似,不过这里保存的是操作传输的字节数
  • blkio.reset_stats:重置统计数据,往该文件中写入一个整数值即可
  • blkio.time:统计 cgroup 对各个设备的访问时间,格式为 major:minor milliseconds
  • blkio.io_serviced:CFQ 调度器下,cgroup 对设备的各种操作次数,和 blkio.throttle.io_serviced 刚好相反,所有不是 throttle 下的请求
  • blkio.io_services_bytes:CFQ 调度器下,cgroup 对各种设备的操作字节数
  • blkio.sectors:cgroup 中传输的扇区次数,格式为 major:minor sector_count
  • blkio.queued:cgroup IO 请求进队列的次数,格式为 number operation
  • blkio.dequeue:cgroup 的 IO 请求被设备出队列的次数,格式为 major:minor number
  • blkio.avg_queue_size:
  • blkio.merged:cgroup 把 BIOS 请求合并到 IO 操作请求的次数,格式为 number operation
  • blkio.io_wait_time:cgroup 等待队列服务的时间
  • blkio.io_service_time:CFQ 调度器下,cgroup 处理请求的时间(从请求开始调度,到 IO 操作完成)

cpu:限制进程组 CPU 使用

CPU 子资源可以管理 cgroup 中任务使用 CPU 的行为,任务使用 CPU 资源有两种调度方式:完全公平调度(CFS,Completely Fair Scheduler)和 实时调度(RT,Real-Time Scheduler)。前者可以根据权重为任务分配响应的 CPU 时间片,后者能够限制使用 CPU 的核数。

CFS 调优参数:

CFS 调度下,每个 cgroup 都会分配一个权重,但是这个权重并不能保证任务使用 CPU 的具体数据。如果只有一个进程在运行(理论上,现实中机器上不太可能只有一个进程),不管它所在 cgroup 对应的 CPU 权重是多少,都能使用所有的 CPU 资源;在 CPU 资源紧张的情况,内核会根据 cgroup 的权重按照比例分配个给任务各自使用 CPU 的时间片。

CFS 调度模式下,也可以给 cgroup 分配一个使用上限,限制任务能使用 CPU 的核数。

设置 CPU 数字的单位都是微秒(microsecond),用 us 表示。

  • cpu.cfs_quota_us:每个周期 cgroup 中所有任务能使用的 CPU 时间,默认为 -1,表示不限制 CPU 使用。需要配合 cpu.cfs_period_us 一起使用,一般设置为 100000(docker 中设置的值)
  • cpu.cfs_period_us:每个周期中 cgroup 任务可以使用的时间周期,如果想要限制 cgroup 任务每秒钟使用 0.5 秒 CPU,可以在 cpu.cfs_quota_us 为 100000 的情况下把它设置为 50000。如果它的值比 cfs_quota_us 大,表明进程可以使用多个核 CPU,比如 200000 表示进程能够使用 2.0 核
  • cpu.stat:CPU 使用的统计数据,nr_periods 表示已经过去的时间周期;nr_throttled 表示 cgroup 中任务被限制使用 CPU 的次数(因为超过了规定的上限);throttled_time 表示被限制的总时间
  • cpu.shares:cgroup 使用 CPU 时间的权重值。如果两个 cgroup 的权重都设置为 100,那么它们里面的任务同时运行时,使用 CPU 的时间应该是一样的;如果把其中一个权重改为 200,那么它能使用的 CPU 时间将是对方的两倍。

RT 调度模式下的参数:

RT 调度模式下和 CFS 中上限设置类似,区别是它只是限制实时任务的 CPU。

  • cpu.rt_period_us:设置一个周期时间,表示多久 cgroup 能够重新分配 CPU 资源
  • cpu.rt_runtime_us:设置运行时间,表示在周期时间内 cgroup 中任务能访问 CPU 的时间。这个限制是针对单个 CPU 核数的,如果是多核,需要乘以对应的核数

cpuacct: 任务使用 CPU 情况统计

cpuacct 不做任何资源限制,它的功能是资源统计,自动地统计 cgroup 中任务对 CPU 资源的使用情况,统计数据也包括子 cgroup 中的任务。

  • cpuacct.usage:该 cgroup 中所有任务(包括子 cgroup 中的任务,下同)总共使用 CPU 的时间,单位是纳秒(ns)。往文件中写入 0 可以重置统计数据
  • cpuacct.stat:该 cgroup 中所有任务使用 CPU 的user 和 system 时间,也就是用户态 CPU 时间和内核态 CPU 时间
  • cpuacct.usage_percpu:该 cgroup 中所有任务使用各个 CPU 核数的时间,单位为纳秒(ns)

cpuset: cpu 绑定

除了限制 CPU 的使用量,cgroup 还能把任务绑定到特定的 CPU,让它们只运行在这些 CPU 上,这就是 cpuset 子资源的功能。除了 CPU 之外,还能绑定内存节点(memory node)。

NOTE:在把任务加入到 cpuset 的 task 文件之前,用户必须设置 cpuset.cpus 和 cpuset.mems 参数。

  • cpuset.cpus:设置 cgroup 中任务能使用的 CPU,格式为逗号(,)隔开的列表,减号(-)可以表示范围。比如,0-2,7 表示 CPU 第 0,1,2,和 7 核。
  • cpuset.mems:设置 cgroup 中任务能使用的内存节点,和 cpuset.cpus 格式一样
    上面两个是最常用的参数,cpuset 中有很多其他参数,需要对 CPU 调度机制有深入的了解,很少用到,而且我也不懂,所以就不写了,具体可以参考参考文档中 RedHat 网站。

memory:限制内存使用

memory 子资源系统能限制 cgroup 中任务对内存的使用,也能生成它们使用数据的报告。

控制内存使用:

  • memory.limit_in_bytes:cgroup 能使用的内存上限值,默认为字节;也可以添加 k/K、m/M 和 g/G 单位后缀。往文件中写入 -1 来移除设置的上限,表示不对内存做限制
  • memory.memsw.limit_in_bytes:cgroup 能使用的内存加 swap 上限,用法和上面一样。写入 -1 来移除上限
  • memory.failcnt:任务使用内存量达到 limit_in_bytes 上限的次数
  • memory.memsw.failcnt:任务使用内存加 swap 量达到 memsw.limit_in_bytes 上限的次数
  • memory.soft_limit_in_bytes:设置内存软上限。如果内存充足, cgroup 中的任务可以用到 memory.limit_in_bytes 设定的内存上限;当时当内存资源不足时,内核会让任务使用的内存不超过 soft_limit_in_bytes 中的值。文件内容的格式和 limit_in_bytes 一样
  • memory.swappiness:设置内核 swap out 进程内存(而不是从 page cache 中回收页) 的倾向。默认值为 60,低于 60 表示降低倾向,高于 60 表示增加倾向;如果值高于 100,表示允许内核 swap out 进程地址空间的页。如果值为 0 表示倾向很低,而不是禁止该行为。

OOM 操作:

OOM 是 out of memory 的缩写,可以翻译成内存用光。cgroup 可以控制内存用完之后应该怎么处理进程,默认情况下,用光内存的进程会被杀死。

memory.oom_control:是否启动 OOM killer,如果启动(值为 0,是默认情况)超过内存限制的进程会被杀死;如果不启动(值为 1),使用超过限定内存的进程不会被杀死,而是被暂停,直到它释放了内存能够被继续使用。

统计内存使用情况:

  • memory.stat:汇报内存的使用情况,里面的数据包括:
    • cache:页缓存(page cache)字节数,包括 tmpfs(shmem)
    • rss:匿名和 swap cache 字节数,不包括 tmpfs
    • mapped_file:内存映射(memory-mapped)的文件大小,包括 tmpfs,单位是字节
    • pgpgin: paged into 内存的页数
    • pgpgout:paged out 内存的页数
    • swap:使用的 swap 字节数
    • active_anon:活跃的 LRU 列表中匿名和 swap 缓存的字节数,包括 tmpfs
      -inactive_anon:不活跃的 LRU 列表中匿名和 swap 缓存的字节数,包括 tmpfs
    • active_file:活跃 LRU 列表中文件支持的(file-backed)的内存字节数
    • inactive_file:不活跃列表中文件支持的(file-backed)的内存字节数
    • unevictable:不可以回收的内存字节数
  • memory.usage_in_bytes:cgroup 中进程当前使用的总内存字节数
  • memory.memsw.usage_in_bytes:cgroup 中进程当前使用的总内存加上总 swap 字节数
  • memory.max_usage_in_bytes:cgroup 中进程使用的最大内存字节数
  • memory.memsw.max_usage_in_bytes:cgroup 中进程使用的最大内存加 swap 字节数

net_cls:为网络报文分类

net_cls 子资源能够给网络报文打上一个标记(classid),这样内核的 tc(traffic control)模块就能根据这个标记做流量控制。

net_cls.classid:包含一个整数值。从文件中读取是的十进制,写入的时候需要是十六进制。比如,0x100001 写入到文件中,读取的将是 1048577, ip 命令操作的形式为 10:1。

这个值的格式为 0xAAAABBBB,一共 32 位,分成前后两个部分,前置的 0 可以忽略,因此 0x10001 和 0x00010001 一样,表示为 1:1。

net_prio:网络报文优先级

net_prio(Network Priority)子资源能够动态设置 cgroup 中应用在网络接口的优先级。网络优先级是报文的一个属性值,tc可以设置网络的优先级,socket 也可以通过 SO_PRIORITY 选项设置它(但是很少应用会这么做)。

  • net_prio.prioidx:只读文件,里面包含了一个整数值,内核用来标识这个 cgroup
  • net_prio.ifpriomap:网络接口的优先级,里面可以包含很多行,用来为从网络接口中发出去的报文设置优先级。每行的格式为 network_interface priority,比如 echo "eth0 5" > /sys/fs/cgroup/net_prio/mycgroup/net_prio.ifpriomap

devices:设备黑白名单

device 子资源可以允许或者阻止 cgroup 中的任务访问某个设备,也就是黑名单和白名单的作用。

  • devices.allow:cgroup 中的任务能够访问的设备列表,格式为 type major:minor access,
    • type 表示类型,可以为 a(all), c(char), b(block)
    • major:minor 代表设备编号,两个标号都可以用* 代替表示所有,比如 : 代表所有的设备
    • accss 表示访问方式,可以为 r(read),w(write), m(mknod) 的组合
  • devices.deny:cgroup 中任务不能访问的设备,和上面的格式相同
  • devices.list:列出 cgroup 中设备的黑名单和白名单

freezer

freezer 子资源比较特殊,它并不和任何系统资源相关,而是能暂停和恢复 cgroup 中的任务。

  • freezer.state:这个文件值存在于非根 cgroup 中(因为所有的任务默认都在根 cgroup 中,停止所有的任务显然是错误的行为),里面的值表示 cgroup 中进程的状态:
    • FROZEN:cgroup 中任务都被挂起(暂停)
    • FREEZING:cgroup 中任务正在被挂起的过程中
    • THAWED:cgroup 中的任务已经正常恢复

要想挂起某个进程,需要先把它移动到某个有 freezer 的 cgroup 中,然后 Freeze 这个 cgroup。

NOTE:如果某个 cgroup 处于挂起状态,不能往里面添加任务。用户可以写入 FROZEN 和 THAWED 来控制进程挂起和恢复,FREEZING 不受用户控制。

总结

cgroup 提供了强大的功能,能够让我们控制应用的资源使用情况,也能统计资源使用数据,是容器技术的基础。但是 cgroup 整个系统也很复杂,甚至显得有些混乱,目前 cgroup 整个在被重写,新的版本被称为 cgroup V2,而之前的版本也就被称为了 V1。

cgroup 本身并不提供对网络资源的使用控制,只能添加简单的标记和优先级,具体的控制需要借助 linux 的 TC 模块来实现。

Systemd基础知识及常规操作

一、前言

上了俩个月的RHCE工程师的班,收获颇多。话说回来,在 redhat 7 中有个非常重要的概念,即:systemd

systemd 是 Linux 下的一款系统和服务管理器,兼容 SysV 和 LSB 的启动脚本。systemd 的特性有:支持并行化任务;同时采用 socket 式与 D-Bus 总线式激活服务;按需启动守护进程(daemon);利用 Linux 的 cgroups 监视进程;支持快照和系统恢复;维护挂载点和自动挂载点;各服务间基于依赖关系进行精密控制。

二、基础知识

监视和控制 systemd 的主要命令是 systemctl。

该命令可用于查看系统状态和管理系统及服务。详见 man 1 systemctl。

使用单元

一个单元配置文件可以描述如下内容之一:系统服务(.service)、挂载点(.mount)、sockets(.sockets) 、系统设备(.device)、交换分区(.swap)、文件路径(.path)、启动目标(.target)、由 systemd 管理的计时器(.timer)。详情参阅 man 5 systemd.unit 。

使用 systemctl 控制单元时,通常需要使用单元文件的全名,包括扩展名(例如 sshd.service )。但是有些单元可以在 systemctl 中使用简写方式。

  • 如果无扩展名,systemctl 默认把扩展名当作 .service 。例如 netcfg 和 netcfg.service 是等价的。
  • 挂载点会自动转化为相应的 .mount 单元。例如 /home 等价于 home.mount 。
  • 设备会自动转化为相应的 .device 单元,所以 /dev/sda2 等价于 dev-sda2.device 。

单元位置

所有可用的单元文件存放在 /usr/lib/systemd/system/ 和 /etc/systemd/system/ 目录(后者优先级更高)。注意,当 /usr/lib/ 中的单元文件因软件包升级变更时,/etc/ 中自定义的单元文件不会同步更新。要注意当单元文件被COPY至并完成修改后,一定要使用 systemctl daemon-reload 完成重载与应用。

编写单元

systemd 单元文件的语法来源于 XDG 桌面项配置文件.desktop文件,最初的源头则是Microsoft Windows的.ini文件。单元文件可以从两个地方加载,优先级从低到高分别是:

  • /usr/lib/systemd/system/ :软件包安装的单元
  • /etc/systemd/system/ :系统管理员安装的单元

  • 当 systemd 运行在用户模式下时,使用的加载路径是完全不同的。

  • systemd 单元名仅能包含 ASCII 字符,下划线和点号。其它字符需要用 C-style “x2d” 替换。

单元文件的语法,可以参考系统已经安装的单元,也可以参考 man systemd.service 中的EXAMPLES章节。

提示: 以 # 开头的注释可能也能用在 unit-files 中,但是只能在新行中使用。不要在 systemd 的参数后面使用行末注释, 否则 unit 将会启动失败。

处理依赖关系

使用 systemd 时,可通过正确编写单元配置文件来解决其依赖关系。典型的情况是,单元 A 要求单元 B 在 A 启动之前运行。在此情况下,向单元 A 配置文件中的 [Unit] 段添加 Requires=B 和 After=B 即可。若此依赖关系是可选的,可添加 Wants=B 和 After=B 。请注意 Wants= 和 Requires= 并不意味着 After= ,即如果 After= 选项没有制定,这两个单元将被并行启动。

依赖关系通常被用在服务(service)而不是目标(target)上。例如, network.target 一般会被某个配置网络接口的服务引入,所以,将自定义的单元排在该服务之后即可,因为 network.target 已经启动。

服务类型

编写自定义的 service 文件时,可以选择几种不同的服务启动方式。启动方式可通过配置文件 [Service] 段中的 Type= 参数进行设置。

  • Type=simple :(默认值) systemd认为该服务将立即启动。服务进程不会 fork 。如果该服务要启动其他服务,不要使用此类型启动,除非该服务是socket 激活型。

  • Type=forking :systemd认为当该服务进程fork,且父进程退出后服务启动成功。对于常规的守护进程(daemon),除非你确定此启动方式无法满足需求,使用此类型启动即可。使用此启动类型应同时指定 PIDFile=,以便 systemd 能够跟踪服务的主进程。

  • Type=oneshot :这一选项适用于只执行一项任务、随后立即退出的服务。可能需要同时设置 RemainAfterExit=yes 使得 systemd 在服务进程退出之后仍然认为服务处于激活状态。

  • Type=notify :与 Type=simple 相同,但约定服务会在就绪后向 systemd 发送一个信号。这一通知的实现由 libsystemd-daemon.so 提供。

  • Type=dbus :若以此方式启动,当指定的 BusName 出现在DBus系统总线上时,systemd认为服务就绪。

  • Type=idle :systemd会等待所有任务处理完成后,才开始执行 idle 类型的单元。其他行为与 Type=simple 类似。

type 的更多解释可以参考 systemd.service(5)。

三、常规操作

快速了解

输出激活的单元

systemctl

审查系统运行状态

systemctl status

注意:这条命令也用于检查服务状态,可在其后加入服务名称或PID号即可查询。缺省下展示所有服务信息。

输出运行失败的单元

systemctl --failed

立即激活单元:

# systemctl start <单元>

常规使用

立即激活单元:

# systemctl start <单元>

立即停止单元:

# systemctl stop <单元>

重启单元:

# systemctl restart <单元>

重新加载配置:

# systemctl reload <单元>

输出单元运行状态:

$ systemctl status <单元>

注意:在输出显示中有Docs:标签,其内容即为:由单元文件提供的手册页;使用-l 参数可查看完整的服务状态日志(提取至内存,即最新)。

检查单元是否配置为自动启动(单):

$ systemctl is-enabled <单元>

检查单元是否配置为自动启动(全):

$ systemctl list-unit-files

注意:你并不需要立马背出该语句,在 chkconfig 命令中有写。依旧可以使用它完成服务的自启或取消自启,但使用 chkconfig 已经不能显示服务的状态了。

开机自动激活单元:

# systemctl enable <单元>

取消开机自动激活单元:

# systemctl disable <单元>

禁用一个单元(禁用后,间接启动也是不可能的):

# systemctl mask <单元>

取消禁用一个单元:

# systemctl unmask <单元>

重新载入 systemd,扫描新的或有变动的单元:

# systemctl daemon-reload

更多操作

修改默认运行级别/目标

开机启动的目标是 default.target,默认链接到 graphical.target (大致相当于原来的运行级别5)。可以通过内核参数更改默认运行级别:

  • systemd.unit=multi-user.target (大致相当于级别3)
  • systemd.unit=rescue.target (大致相当于级别1)

另一个方法是修改 default.target。可以通过 systemctl 修改它:

# systemctl set-default multi-user.target

要覆盖已经设置的default.target,请使用 force:

# systemctl set-default -f multi-user.target

可以在 systemctl 的输出中看到命令执行的效果:链接 /etc/systemd/system/default.target 被创建,指向新的默认运行级别。

切换运行级别/目标

systemd 中,运行级别通过“目标单元”访问。通过如下命令切换:

# systemctl isolate graphical.target

该命令对下次启动无影响。等价于telinit 3 或 telinit 5。

未分类

四、一些彩蛋

显示系统自动时间

# systemd-analyze blame

输出系统启动图片

systemd-analyze plot > bootchart.svg
systemd-analyze dot | dot -Tsvg > bootchart.svg

未分类

注意:这俩个彩蛋依赖于画图工具,需进行安装。yum -y install plot graphviz

(完)

centos7 yum 安装jewel版本ceph

删除默认的源,国外的比较慢

yum clean all
rm -rf /etc/yum.repos.d/*.repo

下载阿里云的base源

wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo

下载阿里云的epel源

wget -O /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repo

修改里面的系统版本为7.3.1611,当前用的centos的版本的的yum源可能已经清空了

sed -i '/aliyuncs/d' /etc/yum.repos.d/CentOS-Base.repo
sed -i '/aliyuncs/d' /etc/yum.repos.d/epel.repo
sed -i 's/$releasever/7.3.1611/g' /etc/yum.repos.d/CentOS-Base.repo

添加ceph源

vim /etc/yum.repos.d/ceph.repo

添加

[ceph]
name=ceph
baseurl=http://mirrors.aliyun.com/ceph/rpm-jewel/el7/x86_64/
gpgcheck=0
[ceph-noarch]
name=cephnoarch
baseurl=http://mirrors.aliyun.com/ceph/rpm-jewel/el7/noarch/
gpgcheck=0
[ceph-source]
name=cephsource
baseurl=http://mirrors.aliyun.com/ceph/rpm-infernalis/el7/SRPMS
gpgcheck=0
[ceph-radosgw]
name=cephradosgw
baseurl=http://mirrors.aliyun.com/ceph/rpm-jewel/el7/x86_64/
gpgcheck=0

进行yum的makecache

yum makecache

安装软件

yum install ceph ceph-deploy

然后我们可以顺利按照官网来安装ceph cluster了

Centos7 YUM安装Percona XtraDB 5.7

node1    10.0.3.111
node2    10.0.3.112
node3    10.0.3.113
注意:避免创建两个或任意偶数个节点集群,因为这可能会导致脑裂。

关闭selinux

sed -i 's/SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config
setenforce 0

设置防火墙

确保以下端口未被防火墙阻止或其他软件使用。Percona XtraDB集群需要它们进行通信。

3306
4444
4567
4568

安装 node1

yum install -y http://www.percona.com/downloads/percona-release/redhat/0.1-4/percona-release-0.1-4.noarch.rpm
yum install -y Percona-XtraDB-Cluster-57
mkdir -p /data/app/mysql/xtradb/{data,logs}
touch /data/app/mysql/xtradb/logs/{data-slow.log,error.log}
chown mysql:mysql /data/app/mysql/xtradb -R
mysqld --initialize --datadir=/data/app/mysql/xtradb/data --user=mysql
mv /etc/my.cnf /etc/my.cnf_bak
mv /etc/percona-xtradb-cluster.cnf /etc/percona-xtradb-cluster.cnf_bak
vi /etc/my.cnf
[client]
port=3306
socket=/var/lib/mysql/mysql.sock
default-character-set=utf8
[mysqld_safe]
socket=/var/lib/mysql/mysql.sock
nice=0
[mysqld]
server-id=111
user=mysql
pid-file=/var/run/mysqld/mysqld.pid
socket=/var/lib/mysql/mysql.sock
port=3306
datadir=/data/app/mysql/xtradb/data/
log_error = /data/app/mysql/xtradb/logs/error.log
skip-external-locking
character-set-server=utf8
max_connections=10000
slow_query_log=1
slow_query_log_file=/data/app/mysql/xtradb/logs/data-slow.log
long_query_time=5
log_queries_not_using_indexes=1
bind-address=0.0.0.0
max_allowed_packet=1024M
myisam_recover_options=BACKUP
query_cache_limit=1M
query_cache_size=16M
expire_logs_days=10
log-bin-trust-function-creators=1
log-bin=bin-log
innodb_buffer_pool_size = 4G
sync_binlog=1
explicit_defaults_for_timestamp = 1
innodb_flush_method = O_DIRECT
disable_ssl
binlog_format=ROW
default_storage_engine=InnoDB
wsrep_provider=/usr/lib64/galera3/libgalera_smm.so
wsrep_cluster_address=gcomm://10.0.3.111,10.0.3.112,10.0.3.113
wsrep_slave_threads=16
wsrep_cluster_name=my_centos_cluster
wsrep_sst_method=xtrabackup-v2
wsrep_node_name=node1
wsrep_node_address=10.0.3.111
wsrep_sst_auth="sstuser:P@ssw0rd"
innodb_locks_unsafe_for_binlog=1
innodb_autoinc_lock_mode=2
sql_mode=NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES
systemctl start [email protected]
mysqladmin -uroot -p`cat /var/log/mysqld.log | grep 'temporary password' | awk '{print $NF}'` password 'P@ssw0rd'
mysql -uroot -p'P@ssw0rd'
mysql> CREATE USER 'sstuser'@'localhost' IDENTIFIED BY 'P@ssw0rd';
mysql> GRANT RELOAD, LOCK TABLES, PROCESS, REPLICATION CLIENT ON *.* TO 'sstuser'@'localhost';
mysql> FLUSH PRIVILEGES;

安装 node2

yum install -y http://www.percona.com/downloads/percona-release/redhat/0.1-4/percona-release-0.1-4.noarch.rpm
yum install -y Percona-XtraDB-Cluster-57
mkdir -p /data/app/mysql/xtradb/{data,logs}
touch /data/app/mysql/xtradb/logs/{data-slow.log,error.log}
chown mysql:mysql /data/app/mysql/xtradb -R
mysqld --initialize --datadir=/data/app/mysql/xtradb/data --user=mysql
mv /etc/my.cnf /etc/my.cnf_bak
mv /etc/percona-xtradb-cluster.cnf /etc/percona-xtradb-cluster.cnf_bak
vi /etc/my.cnf
[client]
port=3306
socket=/var/lib/mysql/mysql.sock
default-character-set=utf8
[mysqld_safe]
socket=/var/lib/mysql/mysql.sock
nice=0
[mysqld]
server-id=112
user=mysql
pid-file=/var/run/mysqld/mysqld.pid
socket=/var/lib/mysql/mysql.sock
port=3306
datadir=/data/app/mysql/xtradb/data/
log_error = /data/app/mysql/xtradb/logs/error.log
skip-external-locking
character-set-server=utf8
max_connections=10000
slow_query_log=1
slow_query_log_file=/data/app/mysql/xtradb/logs/data-slow.log
long_query_time=5
log_queries_not_using_indexes=1
bind-address=0.0.0.0
max_allowed_packet=1024M
myisam_recover_options=BACKUP
query_cache_limit=1M
query_cache_size=16M
expire_logs_days=10
log-bin-trust-function-creators=1
log-bin=bin-log
innodb_buffer_pool_size = 4G
sync_binlog=1
explicit_defaults_for_timestamp = 1
innodb_flush_method = O_DIRECT
disable_ssl
binlog_format=ROW
default_storage_engine=InnoDB
wsrep_provider=/usr/lib64/galera3/libgalera_smm.so
wsrep_cluster_address=gcomm://10.0.3.111,10.0.3.112,10.0.3.113
wsrep_slave_threads=16
wsrep_cluster_name=my_centos_cluster
wsrep_sst_method=xtrabackup-v2
wsrep_node_name=node2
wsrep_node_address=10.0.3.112
wsrep_sst_auth="sstuser:P@ssw0rd"
innodb_locks_unsafe_for_binlog=1
innodb_autoinc_lock_mode=2
sql_mode=NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES
systemctl start mysql.service

安装 node3

yum install -y http://www.percona.com/downloads/percona-release/redhat/0.1-4/percona-release-0.1-4.noarch.rpm
yum install -y Percona-XtraDB-Cluster-57
mkdir -p /data/app/mysql/xtradb/{data,logs}
touch /data/app/mysql/xtradb/logs/{data-slow.log,error.log}
chown mysql:mysql /data/app/mysql/xtradb -R
mv /etc/my.cnf /etc/my.cnf_bak
mv /etc/percona-xtradb-cluster.cnf /etc/percona-xtradb-cluster.cnf_bak
vi /etc/my.cnf
[client]
port=3306
socket=/var/lib/mysql/mysql.sock
default-character-set=utf8
[mysqld_safe]
socket=/var/lib/mysql/mysql.sock
nice=0
[mysqld]
server-id=113
user=mysql
pid-file=/var/run/mysqld/mysqld.pid
socket=/var/lib/mysql/mysql.sock
port=3306
datadir=/data/app/mysql/xtradb/data/
log_error = /data/app/mysql/xtradb/logs/error.log
skip-external-locking
character-set-server=utf8
max_connections=10000
slow_query_log=1
slow_query_log_file=/data/app/mysql/xtradb/logs/data-slow.log
long_query_time=5
log_queries_not_using_indexes=1
bind-address=0.0.0.0
max_allowed_packet=1024M
myisam_recover_options=BACKUP
query_cache_limit=1M
query_cache_size=16M
expire_logs_days=10
log-bin-trust-function-creators=1
log-bin=bin-log
innodb_buffer_pool_size = 4G
sync_binlog=1
explicit_defaults_for_timestamp = 1
innodb_flush_method = O_DIRECT
disable_ssl
binlog_format=ROW
default_storage_engine=InnoDB
wsrep_provider=/usr/lib64/galera3/libgalera_smm.so
wsrep_cluster_address=gcomm://10.0.3.111,10.0.3.112,10.0.3.113
wsrep_slave_threads=16
wsrep_cluster_name=my_centos_cluster
wsrep_sst_method=xtrabackup-v2
wsrep_node_name=node3
wsrep_node_address=10.0.3.113
wsrep_sst_auth="sstuser:P@ssw0rd"
innodb_locks_unsafe_for_binlog=1
innodb_autoinc_lock_mode=2
sql_mode=NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES
systemctl start mysql.service