如何使用redis缓存散景图

How to cache bokeh plots using redis

我正在使用散景服务器在地图上呈现时间序列图。随着时间序列的推进,地图的焦点移动。

下面的代码有效,但每个进程都会创建一个调用 google api (GMAP) 以获取背景。这需要时间来渲染。在时间序列快速连续移动焦点几次的点,背景在更新之前没有时间渲染。

我一直在努力解决if/how这些请求可以提前进行,缓存(使用redis),使用户能够查看缓存,其中包含已为每个滴答加载的所有数据时间序列。

main.py

import settings
from bokeh.plotting import figure, gmap
from bokeh.embed import components
from bokeh.models import CustomJS, ColumnDataSource, Slider, GMapOptions, GMapPlot, Range1d, Button
from bokeh.models.widgets import DataTable, TableColumn, HTMLTemplateFormatter
from bokeh.layouts import column, row, gridplot, layout
from bokeh.io import show, export_png, curdoc

from filehandler import get_graph_data


"""
Get arguments from request
"""
try:
    args = curdoc().session_context.request.arguments
    pk = int(args.get('pk')[0])
except:
    pass
"""
get data for graph from file and initialise variables
"""
#load data into dictionary from file referenced by pk
data_dict = get_graph_data(pk)

no_of_markers = data_dict.get('markers') 
length_of_series = data_dict.get('length')
series_data = data_dict.get('data') #lat/lon position of each series at each point in time
series_names = series_data.get('series_names') #names of series
range_x_axis = data_dict.get('xaxis') #min/max lat co-ords
range_y_axis = data_dict.get('yaxis') #min/max lon co-ords


"""
Build data
"""
graph_source = ColumnDataSource(series_data)

"""
Build markers to show current location
"""
markers = ColumnDataSource(data=dict(lon=[], lat=[]))

"""
Build mapping layer
"""
def create_map_backdrop(centroid, zoom, tools):
    """
    Create the map backdrop, centered on the starting point
    Using GoogleMaps api
    """
    map_options = GMapOptions(lng=centroid[1],
                              lat=centroid[0],
                              map_type='roadmap',
                              zoom=zoom,
                              )

    return gmap(google_api_key=settings.MAP_KEY,
                map_options=map_options,
                tools=tools,
                )

#set map focus
centroid = (graph_source.data['lats'][0][0],
            graph_source.data['lons'][0][0],
            )


"""
Build Plot
"""

tools="pan, wheel_zoom, reset"
p = create_map_backdrop(centroid, 18, tools)
p.multi_line(xs='lons',
             ys='lats',
             source=graph_source,
             line_color='color',
             )
p.toolbar.logo = None
p.circle(x='lon', y='lat', source=markers)


"""
User Interactions
"""

def animate_update():
    tick = slider.value + 1
    slider.value = tick

def slider_update(attr, old, new):
    """
    Updates all of the datasources, depending on current value of slider
    """
    start = timer()
    if slider.value>series_length:
        animate()
    else:
        tick = slider.value
        i=0
        lons, lats = [], []
        marker_lons, marker_lats = [], []

        while i < no_of_markers:

            #update lines
            lons.append(series_data['lons'][i][0:tick])
            lats.append(series_data['lats'][i][0:tick])

            #update markers
            marker_lons.append(series_data['lons'][i][tick])
            marker_lats.append(series_data['lats'][i][tick])

            #update iterators
            i += 1

        #update marker display
        markers.data['lon'] = marker_lons
        markers.data['lat'] = marker_lats

        #update line display
        graph_source.data['lons'] = lons
        graph_source.data['lats'] = lats    

        #set map_focus
        map_focus_lon = series_data['lons'][tick]
        map_focus_lat = series_data['lats'][tick]

        #update map focus
        p.map_options.lng = map_focus_lon
        p.map_options.lat = map_focus_lat



slider = Slider(start=0, end=series_length, value=0, step=5)
slider.on_change('value', slider_update)
callback_id = None

def animate():
    global callback_id
    if button.label == "► Play":
        button.label = "❚❚ Pause"
        callback_id = curdoc().add_periodic_callback(animate_update, 1)

    else:
        button.label = "► Play"
        curdoc().remove_periodic_callback(callback_id)

button = Button(label="► Play", width=60)
button.on_click(animate)

"""
Display plot
"""


grid = layout([[p, data_table],
                [slider, button],
                ])

curdoc().add_root(grid)

我试过缓存绘图数据 (p),但看起来这在调用 google api 之前一直存在。

我探索了直接从 api 缓存地图图块,然后将它们作为背景图像拼接到绘图中(使用 bokeh ImageURL),但我无法让 ImageUrl 识别 in-内存图像。

服务器文档建议可以将 redis 用作后端,所以我想知道这是否可以加快速度,但是当我尝试启动它时 bokeh serve myapp --allow-websocket-origin=127.0.0.1:5006 --backend=redis 我得到 --backend is not a recognised command.

有没有一种方法可以缓存完全呈现的图形(可能是图形文档本身),同时保留用户与绘图交互的能力;或者在渲染后缓存 gmap 图,然后将其添加到图的其余部分?

如果这是独立的 Bokeh 内容(即不是 Bokeh 服务器应用程序),那么您可以使用 json_items 序列化绘图的 JSON 表示,并在浏览器中使用 Bokeh.embed_items。 JSON 可能会存储在 Redis 中,这可能是相关的。但是 Bokeh 服务器不是那样的。在初始会话创建之后,永远不会有任何 "whole document" 存储或缓存,只有通过 websocket 协议发生的一系列增量、部分更新。例如。服务器说 "this specific data source changed" 而浏览器说 "OK I should recompute bounds and re-render".

也就是说,我建议进行一些更改。

首先,您不应该一个一个地更新 CDS 列。你不应该这样做:

# BAD
markers.data['lon'] = marker_lons
markers.data['lat'] = marker_lats

这将生成两个单独的更新事件和两个单独的重新呈现请求。除了这导致的额外工作之外,第一次更新保证具有不匹配的 old/new 坐标也是这种情况。相反,您应该始终一次性更新 CDS .data dict "atomically":

source.data = new_data_dict

此外,您可以尝试 curdoc().hold 将更新收集到更少的事件中。